kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'black' into 'main'
Redesign desktop layout See merge request soapbox-pub/soapbox!3312merge-requests/3313/head
commit
31c461b43f
|
@ -22,7 +22,7 @@ const AnnouncementsPanel = () => {
|
|||
|
||||
return (
|
||||
<Widget title={<FormattedMessage id='announcements.title' defaultMessage='Announcements' />}>
|
||||
<Card className='relative black:rounded-xl black:border black:border-gray-800' size='md' variant='rounded'>
|
||||
<Card className='relative black:rounded-xl black:border black:border-gray-800' size='md'>
|
||||
<ReactSwipeableViews animateHeight index={index} onChangeIndex={handleChangeIndex}>
|
||||
{announcements!.map((announcement) => (
|
||||
<Announcement
|
||||
|
|
|
@ -16,7 +16,7 @@ interface IBigCard {
|
|||
|
||||
const BigCard: React.FC<IBigCard> = ({ title, subtitle, children, onClose }) => {
|
||||
return (
|
||||
<Card variant='rounded' size='xl'>
|
||||
<Card size='xl' rounded>
|
||||
<CardBody className='relative'>
|
||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
||||
<Stack space={2}>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/** Fullscreen gradient used as a backdrop to public pages. */
|
||||
const LandingGradient: React.FC = () => (
|
||||
<div className='fixed h-screen w-full bg-gradient-to-tr from-primary-50 via-white to-gradient-end/10 black:hidden dark:from-primary-900/50 dark:via-primary-900 dark:to-primary-800/50' />
|
||||
);
|
||||
|
||||
export default LandingGradient;
|
|
@ -1,12 +1,9 @@
|
|||
import LandingGradient from 'soapbox/components/landing-gradient.tsx';
|
||||
import Spinner from 'soapbox/components/ui/spinner.tsx';
|
||||
|
||||
/** Fullscreen loading indicator. */
|
||||
const LoadingScreen: React.FC = () => {
|
||||
return (
|
||||
<div className='fixed h-screen w-screen'>
|
||||
<LandingGradient />
|
||||
|
||||
<div className='d-screen fixed z-10 flex w-screen items-center justify-center'>
|
||||
<div className='p-4'>
|
||||
<Spinner size={40} withText={false} />
|
||||
|
|
|
@ -9,7 +9,7 @@ interface MissingIndicatorProps {
|
|||
}
|
||||
|
||||
const MissingIndicator = ({ nested = false }: MissingIndicatorProps): JSX.Element => (
|
||||
<Card variant={nested ? undefined : 'rounded'} size='lg'>
|
||||
<Card rounded={!nested} size='lg'>
|
||||
<CardBody>
|
||||
<Stack space={2}>
|
||||
<Text weight='medium' align='center' size='lg'>
|
||||
|
|
|
@ -3,8 +3,7 @@ import calendarIcon from '@tabler/icons/outline/calendar.svg';
|
|||
import clsx from 'clsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { fetchRelationships } from 'soapbox/actions/accounts.ts';
|
||||
import {
|
||||
|
@ -14,15 +13,20 @@ import {
|
|||
import { useAccount, usePatronUser } from 'soapbox/api/hooks/index.ts';
|
||||
import Badge from 'soapbox/components/badge.tsx';
|
||||
import Markup from 'soapbox/components/markup.tsx';
|
||||
import StillImage from 'soapbox/components/still-image.tsx';
|
||||
import Avatar from 'soapbox/components/ui/avatar.tsx';
|
||||
import { Card, CardBody } from 'soapbox/components/ui/card.tsx';
|
||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||
import Icon from 'soapbox/components/ui/icon.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import VerificationBadge from 'soapbox/components/verification-badge.tsx';
|
||||
import ActionButton from 'soapbox/features/ui/components/action-button.tsx';
|
||||
import { UserPanel } from 'soapbox/features/ui/util/async-components.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
|
||||
import { emojifyText } from 'soapbox/utils/emojify.tsx';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers.tsx';
|
||||
|
||||
import { showProfileHoverCard } from './hover-ref-wrapper.tsx';
|
||||
import { dateFormatOptions } from './relative-timestamp.tsx';
|
||||
|
@ -77,6 +81,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
|
|||
const accountId: string | undefined = useAppSelector(state => state.profile_hover_card.accountId || undefined);
|
||||
const { account } = useAccount(accountId, { withRelationship: true });
|
||||
const { patronUser } = usePatronUser(account?.url);
|
||||
const { displayFqn } = useSoapboxConfig();
|
||||
const targetRef = useAppSelector(state => state.profile_hover_card.ref?.current);
|
||||
const badges = getBadges(account, patronUser);
|
||||
|
||||
|
@ -118,14 +123,75 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
|
|||
onMouseEnter={handleMouseEnter(dispatch)}
|
||||
onMouseLeave={handleMouseLeave(dispatch)}
|
||||
>
|
||||
<Card variant='rounded' className='relative isolate overflow-hidden'>
|
||||
<CardBody>
|
||||
<Stack space={2}>
|
||||
<UserPanel
|
||||
accountId={account.id}
|
||||
action={<ActionButton account={account} small />}
|
||||
badges={badges}
|
||||
/>
|
||||
<Card className='relative isolate overflow-hidden' rounded slim>
|
||||
<CardBody className='relative'>
|
||||
<div className='relative h-24 overflow-hidden bg-gray-200'>
|
||||
<StillImage src={account.header} />
|
||||
</div>
|
||||
|
||||
<Stack space={2} className='-mt-12 px-3 pb-3'>
|
||||
<HStack justifyContent='between'>
|
||||
<Link to={`/@${account.acct}`} title={account.acct}>
|
||||
<Avatar src={account.avatar} size={80} className='size-20 overflow-hidden bg-gray-50 ring-2 ring-white' />
|
||||
</Link>
|
||||
|
||||
<div className='mt-2'>
|
||||
<ActionButton account={account} small />
|
||||
</div>
|
||||
</HStack>
|
||||
|
||||
<Stack>
|
||||
<Link to={`/@${account.acct}`}>
|
||||
<HStack space={1} alignItems='center'>
|
||||
<Text size='lg' weight='bold' truncate>
|
||||
{emojifyText(account.display_name, account.emojis)}
|
||||
</Text>
|
||||
|
||||
{account.verified && <VerificationBadge />}
|
||||
|
||||
{badges && badges.length > 0 && (
|
||||
<HStack space={1} alignItems='center'>
|
||||
{badges}
|
||||
</HStack>
|
||||
)}
|
||||
</HStack>
|
||||
</Link>
|
||||
|
||||
<HStack>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<Text size='sm' theme='muted' direction='ltr' truncate>
|
||||
@{displayFqn ? account.fqn : account.acct}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Stack>
|
||||
|
||||
<HStack alignItems='center' space={3}>
|
||||
{account.followers_count >= 0 && (
|
||||
<Link to={`/@${account.acct}/followers`} title={intl.formatNumber(account.followers_count)}>
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Text theme='primary' weight='bold' size='sm'>
|
||||
{shortNumberFormat(account.followers_count)}
|
||||
</Text>
|
||||
<Text weight='bold' size='sm'>
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{account.following_count >= 0 && (
|
||||
<Link to={`/@${account.acct}/following`} title={intl.formatNumber(account.following_count)}>
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Text theme='primary' weight='bold' size='sm'>
|
||||
{shortNumberFormat(account.following_count)}
|
||||
</Text>
|
||||
<Text weight='bold' size='sm'>
|
||||
<FormattedMessage id='account.follows' defaultMessage='Following' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{account.local ? (
|
||||
<HStack alignItems='center' space={0.5}>
|
||||
|
|
|
@ -36,8 +36,6 @@ interface IPureStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children
|
|||
emptyMessage: React.ReactNode;
|
||||
/** ID of the timeline in Redux. */
|
||||
timelineId?: string;
|
||||
/** Whether to display a gap or border between statuses in the list. */
|
||||
divideType?: 'space' | 'border';
|
||||
/** Whether to display ads. */
|
||||
showAds?: boolean;
|
||||
/** Whether to show group information. */
|
||||
|
@ -51,7 +49,6 @@ const PureStatusList: React.FC<IPureStatusList> = ({
|
|||
statuses,
|
||||
lastStatusId,
|
||||
featuredStatuses,
|
||||
divideType = 'border',
|
||||
onLoadMore,
|
||||
timelineId,
|
||||
isLoading,
|
||||
|
@ -133,7 +130,6 @@ const PureStatusList: React.FC<IPureStatusList> = ({
|
|||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
showGroup={showGroup}
|
||||
variant={divideType === 'border' ? 'slim' : 'rounded'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -161,7 +157,6 @@ const PureStatusList: React.FC<IPureStatusList> = ({
|
|||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
showGroup={showGroup}
|
||||
variant={divideType === 'border' ? 'slim' : 'default'} // shouldn't "default" be changed to "rounded" ?
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
@ -236,15 +231,10 @@ const PureStatusList: React.FC<IPureStatusList> = ({
|
|||
isLoading={isLoading}
|
||||
showLoading={isLoading && statuses.length === 0}
|
||||
onLoadMore={handleLoadOlder}
|
||||
placeholderComponent={() => <PlaceholderStatus variant={divideType === 'border' ? 'slim' : 'rounded'} />}
|
||||
placeholderComponent={() => <PlaceholderStatus />}
|
||||
placeholderCount={20}
|
||||
ref={node}
|
||||
listClassName={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
|
||||
'divide-none': divideType !== 'border',
|
||||
}, className)}
|
||||
itemClassName={clsx({
|
||||
'pb-3': divideType !== 'border',
|
||||
})}
|
||||
listClassName={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', className)}
|
||||
{...other}
|
||||
>
|
||||
{renderScrollableContent()}
|
||||
|
|
|
@ -57,7 +57,6 @@ export interface IPureStatus {
|
|||
featured?: boolean;
|
||||
hideActionBar?: boolean;
|
||||
hoverable?: boolean;
|
||||
variant?: 'default' | 'rounded' | 'slim';
|
||||
showGroup?: boolean;
|
||||
accountAction?: React.ReactElement;
|
||||
}
|
||||
|
@ -80,7 +79,6 @@ const PureStatus: React.FC<IPureStatus> = (props) => {
|
|||
featured,
|
||||
unread,
|
||||
hideActionBar,
|
||||
variant = 'rounded',
|
||||
showGroup = true,
|
||||
} = props;
|
||||
|
||||
|
@ -420,9 +418,9 @@ const PureStatus: React.FC<IPureStatus> = (props) => {
|
|||
role='link'
|
||||
>
|
||||
<Card
|
||||
variant={variant}
|
||||
className={clsx('status--wrapper space-y-4', {
|
||||
'py-6 sm:p-5': variant === 'rounded', muted, read: unread === false,
|
||||
muted,
|
||||
read: unread === false,
|
||||
})}
|
||||
data-id={status.id}
|
||||
>
|
||||
|
|
|
@ -87,7 +87,7 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='fixed left-1/2 top-20 z-50 -translate-x-1/2'>
|
||||
<div className='fixed left-1/2 top-28 z-50 -translate-x-1/2'>
|
||||
<button
|
||||
className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100'
|
||||
onClick={handleClick}
|
||||
|
|
|
@ -169,7 +169,7 @@ const ScrollableList = forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
) : (
|
||||
<>
|
||||
{emptyMessageCard ? (
|
||||
<Card variant='rounded' size='lg'>
|
||||
<Card size='lg'>
|
||||
{emptyMessage}
|
||||
</Card>
|
||||
) : emptyMessage}
|
||||
|
|
|
@ -381,7 +381,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
|||
</button>
|
||||
|
||||
{switcher && (
|
||||
<div className='border-t-2 border-solid border-gray-100 black:border-t dark:border-gray-800'>
|
||||
<div className='border-t border-solid border-gray-100 dark:border-gray-800'>
|
||||
{otherAccounts.map(account => renderAccount(account))}
|
||||
|
||||
<NavLink className='flex items-center space-x-1 py-2' to='/login/add' onClick={handleClose}>
|
||||
|
|
|
@ -44,8 +44,9 @@ const SidebarNavigationLink = forwardRef((props: ISidebarNavigationLink, ref: Re
|
|||
ref={ref}
|
||||
onClick={handleClick}
|
||||
className={clsx({
|
||||
'flex items-center px-4 py-3.5 text-base font-semibold space-x-4 rtl:space-x-reverse rounded-full group text-gray-600 hover:text-gray-900 dark:text-gray-500 dark:hover:text-gray-100 hover:bg-primary-200 dark:hover:bg-primary-900': true,
|
||||
'dark:text-gray-100 text-gray-900': isActive,
|
||||
'flex items-center px-4 py-3.5 text-base font-semibold space-x-4 rtl:space-x-reverse rounded-full group hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-primary-800': true,
|
||||
'text-gray-600 dark:text-gray-500': !isActive,
|
||||
'text-gray-900 dark:text-gray-50': isActive,
|
||||
})}
|
||||
>
|
||||
<span className='relative'>
|
||||
|
@ -54,8 +55,8 @@ const SidebarNavigationLink = forwardRef((props: ISidebarNavigationLink, ref: Re
|
|||
count={count}
|
||||
countMax={countMax}
|
||||
className={clsx('size-5', {
|
||||
'text-gray-600 black:text-white dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400': !isActive,
|
||||
'text-primary-500 dark:text-primary-400': isActive,
|
||||
'text-gray-600 black:text-white dark:text-gray-500 group-hover:text-gray-900 dark:group-hover:text-gray-50': !isActive,
|
||||
'text-gray-900 dark:text-gray-50': isActive,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
|
|
|
@ -21,10 +21,15 @@ import userPlusIcon from '@tabler/icons/outline/user-plus.svg';
|
|||
import userIcon from '@tabler/icons/outline/user.svg';
|
||||
import worldIcon from '@tabler/icons/outline/world.svg';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Account from 'soapbox/components/account.tsx';
|
||||
import SiteLogo from 'soapbox/components/site-logo.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import { useStatContext } from 'soapbox/contexts/stat-context.tsx';
|
||||
import Search from 'soapbox/features/compose/components/search.tsx';
|
||||
import ComposeButton from 'soapbox/features/ui/components/compose-button.tsx';
|
||||
import ProfileDropdown from 'soapbox/features/ui/components/profile-dropdown.tsx';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
|
@ -139,100 +144,119 @@ const SidebarNavigation = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Stack space={4}>
|
||||
<Stack space={2}>
|
||||
<SidebarNavigationLink
|
||||
to='/'
|
||||
icon={homeIcon}
|
||||
activeIcon={homeFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />}
|
||||
/>
|
||||
<Stack justifyContent='between' className='min-h-screen py-6'>
|
||||
<Stack space={6}>
|
||||
<Link key='logo' to='/' data-preview-title-id='column.home' className='ml-4 flex shrink-0 items-center'>
|
||||
<SiteLogo alt='Logo' className='h-10 w-auto cursor-pointer' />
|
||||
<span className='hidden'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></span>
|
||||
</Link>
|
||||
|
||||
<SidebarNavigationLink
|
||||
to='/search'
|
||||
icon={searchIcon}
|
||||
text={<FormattedMessage id='tabs_bar.search' defaultMessage='Discover' />}
|
||||
/>
|
||||
<Search openInRoute autosuggest />
|
||||
|
||||
<Stack space={2}>
|
||||
<SidebarNavigationLink
|
||||
to='/'
|
||||
icon={homeIcon}
|
||||
activeIcon={homeFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />}
|
||||
/>
|
||||
|
||||
<SidebarNavigationLink
|
||||
to='/search'
|
||||
icon={searchIcon}
|
||||
text={<FormattedMessage id='tabs_bar.search' defaultMessage='Discover' />}
|
||||
/>
|
||||
|
||||
{account && (
|
||||
<>
|
||||
<SidebarNavigationLink
|
||||
to='/notifications'
|
||||
icon={bellIcon}
|
||||
activeIcon={bellFilledIcon}
|
||||
count={notificationCount}
|
||||
text={<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />}
|
||||
/>
|
||||
|
||||
{renderMessagesLink()}
|
||||
|
||||
{features.groups && (
|
||||
<SidebarNavigationLink
|
||||
to='/groups'
|
||||
icon={circlesIcon}
|
||||
activeIcon={circlesFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SidebarNavigationLink
|
||||
to={`/@${account.acct}`}
|
||||
icon={userIcon}
|
||||
activeIcon={userFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
||||
/>
|
||||
|
||||
<SidebarNavigationLink
|
||||
to='/settings'
|
||||
icon={settingsIcon}
|
||||
activeIcon={settingsFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.settings' defaultMessage='Settings' />}
|
||||
count={settingsNotifications.size}
|
||||
/>
|
||||
|
||||
{account.staff && (
|
||||
<SidebarNavigationLink
|
||||
to='/soapbox/admin'
|
||||
icon={dashboardIcon}
|
||||
count={dashboardCount}
|
||||
text={<FormattedMessage id='tabs_bar.dashboard' defaultMessage='Dashboard' />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{(features.publicTimeline) && (
|
||||
<>
|
||||
{(account || !restrictUnauth.timelines.local) && (
|
||||
<SidebarNavigationLink
|
||||
to='/timeline/local'
|
||||
icon={features.federating ? atIcon : worldIcon}
|
||||
text={features.federating ? instance.domain : <FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(features.federating && (account || !restrictUnauth.timelines.federated)) && (
|
||||
<SidebarNavigationLink
|
||||
to='/timeline/global'
|
||||
icon={worldIcon}
|
||||
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{menu.length > 0 && (
|
||||
<DropdownMenu items={menu} placement='top'>
|
||||
<SidebarNavigationLink
|
||||
icon={dotsCircleHorizontalIcon}
|
||||
text={<FormattedMessage id='tabs_bar.more' defaultMessage='More' />}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{account && (
|
||||
<>
|
||||
<SidebarNavigationLink
|
||||
to='/notifications'
|
||||
icon={bellIcon}
|
||||
activeIcon={bellFilledIcon}
|
||||
count={notificationCount}
|
||||
text={<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />}
|
||||
/>
|
||||
|
||||
{renderMessagesLink()}
|
||||
|
||||
{features.groups && (
|
||||
<SidebarNavigationLink
|
||||
to='/groups'
|
||||
icon={circlesIcon}
|
||||
activeIcon={circlesFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SidebarNavigationLink
|
||||
to={`/@${account.acct}`}
|
||||
icon={userIcon}
|
||||
activeIcon={userFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
||||
/>
|
||||
|
||||
<SidebarNavigationLink
|
||||
to='/settings'
|
||||
icon={settingsIcon}
|
||||
activeIcon={settingsFilledIcon}
|
||||
text={<FormattedMessage id='tabs_bar.settings' defaultMessage='Settings' />}
|
||||
count={settingsNotifications.size}
|
||||
/>
|
||||
|
||||
{account.staff && (
|
||||
<SidebarNavigationLink
|
||||
to='/soapbox/admin'
|
||||
icon={dashboardIcon}
|
||||
count={dashboardCount}
|
||||
text={<FormattedMessage id='tabs_bar.dashboard' defaultMessage='Dashboard' />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{(features.publicTimeline) && (
|
||||
<>
|
||||
{(account || !restrictUnauth.timelines.local) && (
|
||||
<SidebarNavigationLink
|
||||
to='/timeline/local'
|
||||
icon={features.federating ? atIcon : worldIcon}
|
||||
text={features.federating ? instance.domain : <FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(features.federating && (account || !restrictUnauth.timelines.federated)) && (
|
||||
<SidebarNavigationLink
|
||||
to='/timeline/global'
|
||||
icon={worldIcon}
|
||||
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{menu.length > 0 && (
|
||||
<DropdownMenu items={menu} placement='top'>
|
||||
<SidebarNavigationLink
|
||||
icon={dotsCircleHorizontalIcon}
|
||||
text={<FormattedMessage id='tabs_bar.more' defaultMessage='More' />}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
<ComposeButton />
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{account && (
|
||||
<ComposeButton />
|
||||
<div className='mt-12'>
|
||||
<ProfileDropdown account={account} placement='top'>
|
||||
<div className='w-full p-2'>
|
||||
<Account account={account} showProfileHoverCard={false} withLinkToProfile={false} hideActions />
|
||||
</div>
|
||||
</ProfileDropdown>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
updateStatusHoverCard,
|
||||
} from 'soapbox/actions/status-hover-card.ts';
|
||||
import { fetchStatus } from 'soapbox/actions/statuses.ts';
|
||||
import { Card, CardBody } from 'soapbox/components/ui/card.tsx';
|
||||
import StatusContainer from 'soapbox/containers/status-container.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
|
@ -67,20 +66,7 @@ export const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true })
|
|||
};
|
||||
}, []);
|
||||
|
||||
if (!statusId) return null;
|
||||
|
||||
const renderStatus = (statusId: string) => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
key={statusId}
|
||||
id={statusId}
|
||||
hoverable={false}
|
||||
hideActionBar
|
||||
muted
|
||||
/>
|
||||
);
|
||||
};
|
||||
if (!status) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -94,11 +80,9 @@ export const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true })
|
|||
onMouseEnter={handleMouseEnter()}
|
||||
onMouseLeave={handleMouseLeave()}
|
||||
>
|
||||
<Card className='relative'>
|
||||
<CardBody>
|
||||
{renderStatus(statusId)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
<div className='overflow-hidden rounded-xl bg-white p-4 black:bg-black dark:bg-primary-900'>
|
||||
<StatusContainer id={status.id} hoverable={false} hideActionBar muted slim />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -36,8 +36,6 @@ interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
|
|||
emptyMessage: React.ReactNode;
|
||||
/** ID of the timeline in Redux. */
|
||||
timelineId?: string;
|
||||
/** Whether to display a gap or border between statuses in the list. */
|
||||
divideType?: 'space' | 'border';
|
||||
/** Whether to display ads. */
|
||||
showAds?: boolean;
|
||||
/** Whether to show group information. */
|
||||
|
@ -52,7 +50,6 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
statusIds,
|
||||
lastStatusId,
|
||||
featuredStatusIds,
|
||||
divideType = 'border',
|
||||
onLoadMore,
|
||||
timelineId,
|
||||
isLoading,
|
||||
|
@ -131,7 +128,6 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
onMoveDown={handleMoveDown}
|
||||
contextType={timelineId}
|
||||
showGroup={showGroup}
|
||||
variant={divideType === 'border' ? 'slim' : 'rounded'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -159,7 +155,6 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
onMoveDown={handleMoveDown}
|
||||
contextType={timelineId}
|
||||
showGroup={showGroup}
|
||||
variant={divideType === 'border' ? 'slim' : 'default'}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
@ -234,15 +229,10 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
isLoading={isLoading}
|
||||
showLoading={isLoading && statusIds.size === 0}
|
||||
onLoadMore={handleLoadOlder}
|
||||
placeholderComponent={() => <PlaceholderStatus variant={divideType === 'border' ? 'slim' : 'rounded'} />}
|
||||
placeholderComponent={() => <PlaceholderStatus />}
|
||||
placeholderCount={20}
|
||||
ref={node}
|
||||
listClassName={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
|
||||
'divide-none': divideType !== 'border',
|
||||
}, className)}
|
||||
itemClassName={clsx({
|
||||
'pb-3': divideType !== 'border',
|
||||
})}
|
||||
listClassName={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', className)}
|
||||
{...other}
|
||||
>
|
||||
{renderScrollableContent()}
|
||||
|
|
|
@ -57,9 +57,9 @@ export interface IStatus {
|
|||
featured?: boolean;
|
||||
hideActionBar?: boolean;
|
||||
hoverable?: boolean;
|
||||
variant?: 'default' | 'rounded' | 'slim';
|
||||
showGroup?: boolean;
|
||||
accountAction?: React.ReactElement;
|
||||
slim?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,8 +81,8 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
featured,
|
||||
unread,
|
||||
hideActionBar,
|
||||
variant = 'rounded',
|
||||
showGroup = true,
|
||||
slim,
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
|
@ -417,11 +417,12 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
role='link'
|
||||
>
|
||||
<Card
|
||||
variant={variant}
|
||||
className={clsx('status--wrapper space-y-4', {
|
||||
'py-6 sm:p-5': variant === 'rounded', muted, read: unread === false,
|
||||
muted,
|
||||
read: unread === false,
|
||||
})}
|
||||
data-id={status.id}
|
||||
slim={slim}
|
||||
>
|
||||
{renderStatusInfo()}
|
||||
|
||||
|
|
|
@ -9,23 +9,16 @@ import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
|
|||
import HStack from './hstack.tsx';
|
||||
import Text from './text.tsx';
|
||||
|
||||
const sizes = {
|
||||
md: 'p-4 sm:rounded-xl',
|
||||
lg: 'p-4 sm:p-6 sm:rounded-xl',
|
||||
xl: 'p-4 sm:p-10 sm:rounded-3xl',
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
back: { id: 'card.back.label', defaultMessage: 'Back' },
|
||||
});
|
||||
|
||||
export type CardSizes = keyof typeof sizes
|
||||
|
||||
interface ICard {
|
||||
/** The type of card. */
|
||||
variant?: 'default' | 'rounded' | 'slim';
|
||||
rounded?: boolean;
|
||||
transparent?: boolean;
|
||||
slim?: boolean;
|
||||
/** Card size preset. */
|
||||
size?: CardSizes;
|
||||
size?: 'md' | 'lg' | 'xl';
|
||||
/** Extra classnames for the <div> element. */
|
||||
className?: string;
|
||||
/** Elements inside the card. */
|
||||
|
@ -34,15 +27,16 @@ interface ICard {
|
|||
}
|
||||
|
||||
/** An opaque backdrop to hold a collection of related elements. */
|
||||
const Card = forwardRef<HTMLDivElement, ICard>(({ children, variant = 'default', size = 'md', className, ...filteredProps }, ref): JSX.Element => (
|
||||
const Card = forwardRef<HTMLDivElement, ICard>(({ children, rounded, transparent, slim, size = 'md', className, ...filteredProps }, ref): JSX.Element => (
|
||||
<div
|
||||
ref={ref}
|
||||
{...filteredProps}
|
||||
className={clsx({
|
||||
'bg-white dark:bg-primary-900 black:bg-black text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none': variant === 'rounded',
|
||||
[sizes[size]]: variant === 'rounded',
|
||||
'py-4': variant === 'slim',
|
||||
'black:rounded-none': size !== 'xl',
|
||||
'bg-white dark:bg-primary-900 black:bg-black': !transparent,
|
||||
'overflow-hidden': rounded,
|
||||
'rounded-xl': rounded && size !== 'xl',
|
||||
'rounded-3xl': rounded && size === 'xl',
|
||||
'py-4 px-5': !slim,
|
||||
}, className)}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import clsx from 'clsx';
|
||||
import { throttle } from 'es-toolkit';
|
||||
import { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import Helmet from 'soapbox/components/helmet.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
|
||||
|
||||
import { Card, CardBody, CardHeader, CardTitle, type CardSizes } from './card.tsx';
|
||||
import { Card, CardBody, CardHeader, CardTitle } from './card.tsx';
|
||||
|
||||
type IColumnHeader = Pick<IColumn, 'label' | 'backHref' | 'className' | 'action'>;
|
||||
|
||||
|
@ -47,6 +47,8 @@ export interface IColumn {
|
|||
label?: string;
|
||||
/** Whether this column should have a transparent background. */
|
||||
transparent?: boolean;
|
||||
/** Whether to display the column without padding. */
|
||||
slim?: boolean;
|
||||
/** Whether this column should have a title and back button. */
|
||||
withHeader?: boolean;
|
||||
/** Extra class name for top <div> element. */
|
||||
|
@ -60,26 +62,13 @@ export interface IColumn {
|
|||
/** Action for the ColumnHeader, displayed at the end. */
|
||||
action?: React.ReactNode;
|
||||
/** Column size, inherited from Card. */
|
||||
size?: CardSizes;
|
||||
size?: 'md' | 'lg' | 'xl';
|
||||
}
|
||||
|
||||
/** A backdrop for the main section of the UI. */
|
||||
const Column = forwardRef<HTMLDivElement, IColumn>((props, ref): JSX.Element => {
|
||||
const { backHref, children, label, transparent = false, withHeader = true, className, bodyClassName, action, size } = props;
|
||||
const { backHref, children, label, transparent = false, slim, withHeader = true, className, bodyClassName, action, size } = props;
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
const handleScroll = useCallback(throttle(() => {
|
||||
setIsScrolled(window.pageYOffset > 32);
|
||||
}, 50), []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div role='region' className='relative' ref={ref} aria-label={label} column-type={transparent ? 'transparent' : 'filled'}>
|
||||
|
@ -95,25 +84,25 @@ const Column = forwardRef<HTMLDivElement, IColumn>((props, ref): JSX.Element =>
|
|||
)}
|
||||
</Helmet>
|
||||
|
||||
<Card size={size} variant={transparent ? undefined : 'rounded'} className={className}>
|
||||
<Stack>
|
||||
{withHeader && (
|
||||
<ColumnHeader
|
||||
label={label}
|
||||
backHref={backHref}
|
||||
className={clsx({
|
||||
'rounded-t-3xl': !isScrolled && !transparent,
|
||||
'sticky top-12 z-10 bg-white/90 dark:bg-primary-900/90 black:bg-black/90 backdrop-blur lg:top-16': !transparent,
|
||||
'p-4 sm:p-0 sm:pb-4 black:p-4': transparent,
|
||||
'-mt-4 p-4': size !== 'lg' && !transparent,
|
||||
'-mt-4 p-4 sm:-mt-6 sm:-mx-6 sm:p-6': size === 'lg' && !transparent,
|
||||
className={clsx('px-5 py-4', {
|
||||
'sticky top-12 z-20 bg-white/90 dark:bg-primary-900/90 black:bg-black/90 backdrop-blur lg:top-0': !transparent,
|
||||
'-mb-4': !slim,
|
||||
})}
|
||||
action={action}
|
||||
/>
|
||||
)}
|
||||
<CardBody className={bodyClassName}>
|
||||
{children}
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card size={size} transparent={transparent} className={className} slim={slim}>
|
||||
<CardBody className={bodyClassName}>
|
||||
{children}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ interface IDivider {
|
|||
const Divider = ({ text, textSize = 'md' }: IDivider) => (
|
||||
<div className='relative' data-testid='divider'>
|
||||
<div className='absolute inset-0 flex items-center' aria-hidden='true'>
|
||||
<div className='w-full border-t-2 border-solid border-gray-100 black:border-t dark:border-gray-800' />
|
||||
<div className='w-full border-t border-solid border-gray-100 dark:border-gray-800' />
|
||||
</div>
|
||||
|
||||
{text && (
|
||||
|
|
|
@ -95,7 +95,7 @@ const Input = forwardRef<HTMLInputElement, IInput>(
|
|||
'text-gray-900 dark:text-gray-100': !props.disabled,
|
||||
'text-gray-600': props.disabled,
|
||||
'rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800 black:bg-black': theme === 'normal',
|
||||
'rounded-full bg-gray-200 border-gray-200 dark:bg-gray-800 dark:border-gray-800 focus:bg-white dark:focus:bg-gray-900': theme === 'search',
|
||||
'rounded-full bg-white border-gray-200 dark:bg-gray-900 dark:border-gray-800 py-2.5': theme === 'search',
|
||||
'pr-10 rtl:pl-10 rtl:pr-3': isPassword || append,
|
||||
'pl-8': typeof icon !== 'undefined',
|
||||
'pl-16': typeof prepend !== 'undefined',
|
||||
|
|
|
@ -21,7 +21,7 @@ interface LayoutComponent extends React.FC<ILayout> {
|
|||
|
||||
/** Layout container, to hold Sidebar, Main, and Aside. */
|
||||
const Layout: LayoutComponent = ({ children }) => (
|
||||
<div className='relative flex grow flex-col black:pt-0 sm:pt-4'>
|
||||
<div className='relative flex grow flex-col'>
|
||||
<div className='mx-auto w-full max-w-3xl grow sm:px-6 md:grid md:max-w-7xl md:grid-cols-12 md:gap-8 md:px-8'>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@ const Layout: LayoutComponent = ({ children }) => (
|
|||
/** Left sidebar container in the UI. */
|
||||
const Sidebar: React.FC<ISidebar> = ({ children }) => (
|
||||
<div className='hidden lg:col-span-3 lg:block'>
|
||||
<StickyBox offsetTop={80} className='pb-4'>
|
||||
<StickyBox>
|
||||
{children}
|
||||
</StickyBox>
|
||||
</div>
|
||||
|
@ -39,11 +39,7 @@ const Sidebar: React.FC<ISidebar> = ({ children }) => (
|
|||
|
||||
/** Center column container in the UI. */
|
||||
const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, className }) => (
|
||||
<main
|
||||
className={clsx({
|
||||
'md:col-span-12 lg:col-span-9 xl:col-span-6 pb-36 black:border-gray-800 lg:black:border-l xl:black:border-r': true,
|
||||
}, className)}
|
||||
>
|
||||
<main className={clsx('border-gray-200 bg-white pb-6 black:border-gray-800 black:bg-black dark:border-gray-800 dark:bg-primary-900 md:col-span-12 lg:col-span-9 lg:border-l xl:col-span-6 xl:border-r', className)}>
|
||||
{children}
|
||||
</main>
|
||||
);
|
||||
|
@ -51,7 +47,7 @@ const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, classN
|
|||
/** Right sidebar container in the UI. */
|
||||
const Aside: React.FC<IAside> = ({ children }) => (
|
||||
<aside className='hidden xl:col-span-3 xl:block'>
|
||||
<StickyBox offsetTop={80} className='space-y-6 pb-12'>
|
||||
<StickyBox className='space-y-6 py-6 pb-12'>
|
||||
<Suspense>
|
||||
{children}
|
||||
</Suspense>
|
||||
|
|
|
@ -36,6 +36,6 @@ const MenuList: React.FC<IMenuList> = (props) => {
|
|||
};
|
||||
|
||||
/** Divides menu items. */
|
||||
const MenuDivider = () => <hr className='mx-2 my-1 border-t-2 border-gray-100 black:border-t dark:border-gray-800' />;
|
||||
const MenuDivider = () => <hr className='mx-2 my-1 border-t border-gray-100 dark:border-gray-800' />;
|
||||
|
||||
export { Menu, MenuButton, MenuDivider, MenuItems, MenuItem, MenuList, MenuLink };
|
||||
|
|
|
@ -69,7 +69,7 @@ const AboutPage: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<Card variant='rounded'>
|
||||
<Card>
|
||||
<div className='prose mx-auto py-4 dark:prose-invert sm:p-6'>
|
||||
<div dangerouslySetInnerHTML={{ __html: pageHtml }} />
|
||||
{alsoAvailable}
|
||||
|
|
|
@ -137,9 +137,9 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
|
||||
if (!account) {
|
||||
return (
|
||||
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'>
|
||||
<div>
|
||||
<div>
|
||||
<div className='relative h-32 w-full bg-gray-200 black:rounded-t-none dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
|
||||
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 lg:h-48' />
|
||||
</div>
|
||||
|
||||
<div className='px-4 sm:px-6'>
|
||||
|
@ -679,13 +679,13 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
const acceptsZaps = account.ditto.accepts_zaps === true;
|
||||
|
||||
return (
|
||||
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'>
|
||||
<div>
|
||||
{(account.moved && typeof account.moved === 'object') && (
|
||||
<MovedNote from={account} to={account.moved as Account} />
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 black:rounded-t-none dark:bg-gray-900/50 md:rounded-t-xl lg:h-48'>
|
||||
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 dark:bg-gray-900/50 lg:h-48'>
|
||||
{renderHeader()}
|
||||
|
||||
<div className='absolute left-2 top-2'>
|
||||
|
|
|
@ -101,7 +101,7 @@ const AuthTokenList: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.header)} transparent withHeader={false}>
|
||||
<Card variant='rounded'>
|
||||
<Card>
|
||||
<CardHeader backHref='/settings'>
|
||||
<CardTitle title={intl.formatMessage(messages.header)} />
|
||||
</CardHeader>
|
||||
|
|
|
@ -84,7 +84,7 @@ const Backups = () => {
|
|||
const showLoading = isLoading && backups.count() === 0;
|
||||
|
||||
const emptyMessage = (
|
||||
<Card variant='rounded' size='lg'>
|
||||
<Card size='lg'>
|
||||
{intl.formatMessage(messages.emptyMessage, {
|
||||
action: (
|
||||
<Link to={'/'} className='inline-flex'>
|
||||
|
|
|
@ -5,8 +5,6 @@ import { useBookmarks } from 'soapbox/api/hooks/index.ts';
|
|||
import PullToRefresh from 'soapbox/components/pull-to-refresh.tsx';
|
||||
import PureStatusList from 'soapbox/components/pure-status-list.tsx';
|
||||
import { Column } from 'soapbox/components/ui/column.tsx';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
|
@ -15,9 +13,6 @@ const messages = defineMessages({
|
|||
const Bookmarks: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleLoadMore = debounce(() => {
|
||||
fetchNextPage();
|
||||
}, 300, { edges: ['leading'] });
|
||||
|
@ -31,7 +26,7 @@ const Bookmarks: React.FC = () => {
|
|||
const emptyMessage = <FormattedMessage id='empty_column.bookmarks' defaultMessage="You don't have any bookmarks yet. When you add one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} transparent>
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<PureStatusList
|
||||
className='black:p-4 black:sm:p-5'
|
||||
|
@ -41,7 +36,6 @@ const Bookmarks: React.FC = () => {
|
|||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
onLoadMore={() => handleLoadMore()}
|
||||
emptyMessage={emptyMessage}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
</Column>
|
||||
|
|
|
@ -8,15 +8,12 @@ import { Column } from 'soapbox/components/ui/column.tsx';
|
|||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
import Timeline from '../ui/components/timeline.tsx';
|
||||
|
||||
const CommunityTimeline = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
const { instance } = useInstance();
|
||||
|
||||
const settings = useSettings();
|
||||
|
@ -24,7 +21,6 @@ const CommunityTimeline = () => {
|
|||
const next = useAppSelector(state => state.timelines.get('community')?.next);
|
||||
|
||||
const timelineId = 'community';
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleLoadMore = (maxId: string) => {
|
||||
dispatch(expandCommunityTimeline({ url: next, maxId, onlyMedia }));
|
||||
|
@ -41,7 +37,7 @@ const CommunityTimeline = () => {
|
|||
}, [onlyMedia]);
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={instance.domain} transparent={!isMobile}>
|
||||
<Column label={instance.domain} slim withHeader={false}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
className='black:p-4 black:sm:p-5'
|
||||
|
@ -50,7 +46,6 @@ const CommunityTimeline = () => {
|
|||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
</Column>
|
||||
|
|
|
@ -154,13 +154,11 @@ const SearchResults = () => {
|
|||
|
||||
if (results.statuses && results.statuses.size > 0) {
|
||||
searchResults = results.statuses.map((statusId: string) => (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
key={statusId}
|
||||
id={statusId}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
variant='slim'
|
||||
/>
|
||||
));
|
||||
resultsIds = results.statuses;
|
||||
|
@ -173,7 +171,6 @@ const SearchResults = () => {
|
|||
id={statusId}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
variant='slim'
|
||||
/>
|
||||
));
|
||||
resultsIds = trendingStatuses;
|
||||
|
@ -227,7 +224,9 @@ const SearchResults = () => {
|
|||
/>
|
||||
</Text>
|
||||
</HStack>
|
||||
) : renderFilterBar()}
|
||||
) : (
|
||||
<div className='px-4'>{renderFilterBar()}</div>
|
||||
)}
|
||||
|
||||
{noResultsMessage || (
|
||||
<ScrollableList
|
||||
|
@ -244,7 +243,8 @@ const SearchResults = () => {
|
|||
listClassName={clsx({
|
||||
'divide-gray-200 dark:divide-gray-800 divide-solid divide-y': selectedFilter === 'statuses',
|
||||
})}
|
||||
itemClassName={clsx('px-4', {
|
||||
itemClassName={clsx({
|
||||
'px-4': selectedFilter !== 'statuses',
|
||||
'pb-4': selectedFilter === 'accounts',
|
||||
'pb-3': selectedFilter === 'hashtags',
|
||||
})}
|
||||
|
|
|
@ -52,7 +52,7 @@ const DeleteAccount = () => {
|
|||
}, [password, dispatch, intl]);
|
||||
|
||||
return (
|
||||
<Card variant='rounded'>
|
||||
<Card>
|
||||
<CardHeader backHref='/settings'>
|
||||
<CardTitle title={intl.formatMessage(messages.deleteHeader)} />
|
||||
</CardHeader>
|
||||
|
|
|
@ -36,7 +36,7 @@ const DirectTimeline = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<Column label={intl.formatMessage(messages.heading)} slim>
|
||||
<AccountSearch
|
||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||
onSelected={handleSuggestion}
|
||||
|
@ -47,7 +47,6 @@ const DirectTimeline = () => {
|
|||
timelineId='direct'
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
divideType='border'
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -55,7 +55,7 @@ const EmbeddedStatus: React.FC<IEmbeddedStatus> = ({ params }) => {
|
|||
if (loading) {
|
||||
return <Spinner />;
|
||||
} else if (status) {
|
||||
return <Status status={status} accountAction={logo} variant='default' />;
|
||||
return <Status status={status} accountAction={logo} />;
|
||||
} else {
|
||||
return <MissingIndicator nested />;
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ const EventDiscussion: React.FC<IEventDiscussion> = (props) => {
|
|||
ref={scroller}
|
||||
hasMore={!!next}
|
||||
onLoadMore={handleLoadMore}
|
||||
placeholderComponent={() => <PlaceholderStatus variant='slim' />}
|
||||
placeholderComponent={() => <PlaceholderStatus />}
|
||||
initialTopMostItemIndex={0}
|
||||
emptyMessage={<FormattedMessage id='event.discussion.empty' defaultMessage='No one has commented this event yet. When someone does, they will appear here.' />}
|
||||
>
|
||||
|
|
|
@ -49,7 +49,7 @@ const EventCarousel: React.FC<IEventCarousel> = ({ statusIds, isLoading, emptyMe
|
|||
}
|
||||
|
||||
return (
|
||||
<Card variant='rounded' size='lg'>
|
||||
<Card size='lg'>
|
||||
{emptyMessage}
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -98,7 +98,7 @@ const FeedSuggestions: React.FC<IFeedSuggesetions> = ({ statusId, onMoveUp, onMo
|
|||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<Card size='lg' variant='rounded' className='focusable space-y-6' tabIndex={0}>
|
||||
<Card size='lg' className='focusable space-y-6' tabIndex={0}>
|
||||
<HStack justifyContent='between' alignItems='center'>
|
||||
<CardTitle title={intl.formatMessage(messages.heading)} />
|
||||
|
||||
|
|
|
@ -38,9 +38,9 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
|||
|
||||
if (!group) {
|
||||
return (
|
||||
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6' data-testid='group-header-missing'>
|
||||
<div data-testid='group-header-missing'>
|
||||
<div>
|
||||
<div className='relative h-32 w-full bg-gray-200 black:rounded-t-none dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
|
||||
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 lg:h-48' />
|
||||
</div>
|
||||
|
||||
<div className='px-4 sm:px-6'>
|
||||
|
@ -123,7 +123,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'>
|
||||
<div>
|
||||
<div className='relative'>
|
||||
{renderHeader()}
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ const GroupTagTimeline: React.FC<IGroupTimeline> = (props) => {
|
|||
scrollKey='group_timeline'
|
||||
timelineId={`group:tags:${groupId}:${tag.name}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
divideType='border'
|
||||
showGroup={false}
|
||||
emptyMessageCard={false}
|
||||
emptyMessage={
|
||||
|
|
|
@ -130,7 +130,6 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
|||
</Stack>
|
||||
}
|
||||
emptyMessageCard={false}
|
||||
divideType='border'
|
||||
showGroup={false}
|
||||
featuredStatusIds={featuredStatusIds}
|
||||
/>
|
||||
|
|
|
@ -11,9 +11,7 @@ import Timeline from 'soapbox/features/ui/components/timeline.tsx';
|
|||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useLoggedIn } from 'soapbox/hooks/useLoggedIn.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
interface IHashtagTimeline {
|
||||
params?: {
|
||||
|
@ -29,8 +27,6 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
|
|||
const tag = useAppSelector((state) => state.tags.get(id));
|
||||
const next = useAppSelector(state => state.timelines.get(`hashtag:${id}`)?.next);
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleLoadMore = (maxId: string) => {
|
||||
dispatch(expandHashtagTimeline(id, { url: next, maxId }));
|
||||
|
@ -57,7 +53,7 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
|
|||
}, [id]);
|
||||
|
||||
return (
|
||||
<Column label={`#${id}`} transparent={!isMobile}>
|
||||
<Column label={`#${id}`} slim>
|
||||
{features.followHashtags && isLoggedIn && (
|
||||
<List>
|
||||
<ListItem
|
||||
|
@ -76,7 +72,6 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
|
|||
timelineId={`hashtag:${id}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -12,8 +12,6 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
|||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.home', defaultMessage: 'Home' },
|
||||
|
@ -24,10 +22,8 @@ const HomeTimeline: React.FC = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const { instance } = useInstance();
|
||||
const theme = useTheme();
|
||||
|
||||
const polling = useRef<NodeJS.Timeout | null>(null);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true);
|
||||
const next = useAppSelector(state => state.timelines.get('home')?.next);
|
||||
|
@ -68,14 +64,12 @@ const HomeTimeline: React.FC = () => {
|
|||
}, [isPartial]);
|
||||
|
||||
return (
|
||||
<Column className='py-0' label={intl.formatMessage(messages.title)} transparent={!isMobile} withHeader={false}>
|
||||
<Column label={intl.formatMessage(messages.title)} withHeader={false} slim>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
className='black:p-4 black:sm:p-5'
|
||||
scrollKey='home_timeline'
|
||||
onLoadMore={handleLoadMore}
|
||||
timelineId='home'
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
emptyMessage={
|
||||
<Stack space={1}>
|
||||
<Text size='xl' weight='medium' align='center'>
|
||||
|
|
|
@ -9,7 +9,6 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
|||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
import AboutPage from '../about/index.tsx';
|
||||
import Timeline from '../ui/components/timeline.tsx';
|
||||
|
@ -19,7 +18,6 @@ import { SiteBanner } from './components/site-banner.tsx';
|
|||
const LandingTimeline = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { instance } = useInstance();
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const timelineEnabled = !instance.pleroma.metadata.restrict_unauthenticated.timelines.local;
|
||||
|
@ -48,7 +46,7 @@ const LandingTimeline = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<Column transparent={!isMobile} withHeader={false}>
|
||||
<Column transparent={!isMobile} withHeader={false} slim>
|
||||
<div className='my-12 mb-16 px-4 sm:mb-20'>
|
||||
<SiteBanner />
|
||||
</div>
|
||||
|
@ -62,7 +60,6 @@ const LandingTimeline = () => {
|
|||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
) : (
|
||||
|
|
|
@ -12,16 +12,12 @@ import { Column } from 'soapbox/components/ui/column.tsx';
|
|||
import Spinner from 'soapbox/components/ui/spinner.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
import Timeline from '../ui/components/timeline.tsx';
|
||||
|
||||
const ListTimeline: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const list = useAppSelector((state) => state.lists.get(id));
|
||||
const next = useAppSelector(state => state.timelines.get(`list:${id}`)?.next);
|
||||
|
@ -66,14 +62,13 @@ const ListTimeline: React.FC = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column label={title} transparent={!isMobile}>
|
||||
<Column label={title}>
|
||||
<Timeline
|
||||
className='black:p-4 black:sm:p-5'
|
||||
scrollKey='list_timeline'
|
||||
timelineId={`list:${id}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -19,9 +19,7 @@ import Portal from 'soapbox/components/ui/portal.tsx';
|
|||
import PlaceholderNotification from 'soapbox/features/placeholder/components/placeholder-notification.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
import FilterBar from './components/filter-bar.tsx';
|
||||
import Notification from './components/notification.tsx';
|
||||
|
@ -63,9 +61,6 @@ const Notifications = () => {
|
|||
const hasMore = useAppSelector(state => state.notifications.hasMore);
|
||||
const totalQueuedNotificationsCount = useAppSelector(state => state.notifications.totalQueuedNotificationsCount || 0);
|
||||
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const node = useRef<VirtuosoHandle>(null);
|
||||
const column = useRef<HTMLDivElement>(null);
|
||||
const scrollableContentRef = useRef<ImmutableList<JSX.Element> | null>(null);
|
||||
|
@ -186,7 +181,7 @@ const Notifications = () => {
|
|||
ref={column}
|
||||
label={intl.formatMessage(messages.title)}
|
||||
withHeader={false}
|
||||
className={clsx({ '!p-0': isMobile || theme === 'black' })}
|
||||
className='!p-0'
|
||||
>
|
||||
{filterBarContainer}
|
||||
|
||||
|
|
|
@ -8,18 +8,12 @@ import PlaceholderDisplayName from './placeholder-display-name.tsx';
|
|||
import PlaceholderStatusContent from './placeholder-status-content.tsx';
|
||||
|
||||
interface IPlaceholderStatus {
|
||||
variant?: 'rounded' | 'slim' | 'default';
|
||||
slim?: boolean;
|
||||
}
|
||||
|
||||
/** Fake status to display while data is loading. */
|
||||
const PlaceholderStatus: React.FC<IPlaceholderStatus> = ({ variant }) => (
|
||||
<div
|
||||
className={clsx({
|
||||
'status-placeholder bg-white black:bg-black dark:bg-primary-900': true,
|
||||
'shadow-xl dark:shadow-none sm:rounded-xl px-4 py-6 sm:p-5': variant === 'rounded',
|
||||
'py-4': variant === 'slim',
|
||||
})}
|
||||
>
|
||||
const PlaceholderStatus: React.FC<IPlaceholderStatus> = ({ slim }) => (
|
||||
<div className={clsx('status-placeholder bg-white black:bg-black dark:bg-primary-900', { 'p-4': !slim })}>
|
||||
<div className='w-full animate-pulse overflow-hidden'>
|
||||
<div>
|
||||
<HStack space={3} alignItems='center'>
|
||||
|
|
|
@ -14,9 +14,7 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
|||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
import PinnedHostsPicker from '../remote-timeline/components/pinned-hosts-picker.tsx';
|
||||
import Timeline from '../ui/components/timeline.tsx';
|
||||
|
@ -30,7 +28,6 @@ const PublicTimeline = () => {
|
|||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const theme = useTheme();
|
||||
|
||||
const [language, setLanguage] = useState<string>(localStorage.getItem('soapbox:global:language') || '');
|
||||
|
||||
|
@ -40,7 +37,6 @@ const PublicTimeline = () => {
|
|||
const next = useAppSelector(state => state.timelines.get('public')?.next);
|
||||
|
||||
const timelineId = 'public';
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const explanationBoxExpanded = settings.explanationBox;
|
||||
const showExplanationBox = settings.showExplanationBox && !features.nostr;
|
||||
|
@ -74,10 +70,9 @@ const PublicTimeline = () => {
|
|||
|
||||
return (
|
||||
<Column
|
||||
className='-mt-3 sm:mt-0'
|
||||
label={intl.formatMessage(messages.title)}
|
||||
transparent={!isMobile}
|
||||
action={features.publicTimelineLanguage ? <LanguageDropdown language={language} setLanguage={setLanguage} /> : null}
|
||||
slim
|
||||
>
|
||||
<PinnedHostsPicker />
|
||||
|
||||
|
@ -118,7 +113,6 @@ const PublicTimeline = () => {
|
|||
prefix='home'
|
||||
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' />}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
</Column>
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Column } from 'soapbox/components/ui/column.tsx';
|
|||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.quotes', defaultMessage: 'Post quotes' },
|
||||
|
@ -23,7 +22,6 @@ const Quotes: React.FC = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const { statusId } = useParams<{ statusId: string }>();
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const statusIds = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'items'], ImmutableOrderedSet<string>()));
|
||||
|
@ -51,7 +49,6 @@ const Quotes: React.FC = () => {
|
|||
onLoadMore={() => handleLoadMore(statusId, dispatch)}
|
||||
onRefresh={handleRefresh}
|
||||
emptyMessage={emptyMessage}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -11,9 +11,7 @@ import HStack from 'soapbox/components/ui/hstack.tsx';
|
|||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
import Timeline from '../ui/components/timeline.tsx';
|
||||
|
||||
|
@ -29,7 +27,6 @@ interface IRemoteTimeline {
|
|||
const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
|
||||
const instance = params?.instance as string;
|
||||
const settings = useSettings();
|
||||
|
@ -39,7 +36,6 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
|
|||
const next = useAppSelector(state => state.timelines.get('remote')?.next);
|
||||
|
||||
const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleCloseClick: React.MouseEventHandler = () => {
|
||||
history.push('/timeline/fediverse');
|
||||
|
@ -56,7 +52,7 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
|
|||
}, [onlyMedia]);
|
||||
|
||||
return (
|
||||
<Column label={instance} transparent={!isMobile}>
|
||||
<Column label={instance} slim>
|
||||
{instance && <PinnedHostsPicker host={instance} />}
|
||||
|
||||
{!pinned && (
|
||||
|
@ -84,7 +80,6 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
|
|||
values={{ instance }}
|
||||
/>
|
||||
}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import clsx from 'clsx';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { Column } from 'soapbox/components/ui/column.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import SearchResults from 'soapbox/features/compose/components/search-results.tsx';
|
||||
import Search from 'soapbox/features/compose/components/search.tsx';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.search', defaultMessage: 'Discover' },
|
||||
|
@ -14,20 +12,14 @@ const messages = defineMessages({
|
|||
const SearchPage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Column
|
||||
label={intl.formatMessage(messages.heading)}
|
||||
className={clsx({ '!px-0': isMobile || theme === 'black' })}
|
||||
>
|
||||
<div className='space-y-4'>
|
||||
<div className='px-4 sm:py-0'>
|
||||
<Column label={intl.formatMessage(messages.heading)} slim>
|
||||
<Stack space={4}>
|
||||
<div className='px-4'>
|
||||
<Search autoSubmit />
|
||||
</div>
|
||||
<SearchResults />
|
||||
</div>
|
||||
</Stack>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -68,8 +68,8 @@ const Settings = () => {
|
|||
const displayName = account.display_name || account.username;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.settings)} transparent withHeader={false}>
|
||||
<Card className='space-y-4' variant='rounded'>
|
||||
<Column label={intl.formatMessage(messages.settings)} transparent withHeader={false} slim>
|
||||
<Card className='space-y-4'>
|
||||
<CardHeader>
|
||||
<CardTitle title={intl.formatMessage(messages.profile)} />
|
||||
</CardHeader>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import { defaultSettings } from 'soapbox/actions/settings.ts';
|
||||
import SiteLogo from 'soapbox/components/site-logo.tsx';
|
||||
import BackgroundShapes from 'soapbox/features/ui/components/background-shapes.tsx';
|
||||
import { useSystemTheme } from 'soapbox/hooks/useSystemTheme.ts';
|
||||
import { normalizeSoapboxConfig } from 'soapbox/normalizers/index.ts';
|
||||
import { generateThemeCss } from 'soapbox/utils/theme.ts';
|
||||
|
@ -39,7 +38,6 @@ const SitePreview: React.FC<ISitePreview> = ({ soapbox }) => {
|
|||
<div className={bodyClass}>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<style>{`.site-preview {${generateThemeCss(soapboxConfig)}}`}</style>
|
||||
<BackgroundShapes position='absolute' />
|
||||
|
||||
<div className='absolute z-[2] self-center overflow-hidden rounded-lg bg-accent-500 p-2 text-white'>
|
||||
<FormattedMessage id='site_preview.preview' defaultMessage='Preview' />
|
||||
|
|
|
@ -15,7 +15,7 @@ const ThreadLoginCta: React.FC = () => {
|
|||
if (!displayCta) return null;
|
||||
|
||||
return (
|
||||
<Card className='space-y-6 px-6 py-12 text-center' variant='rounded'>
|
||||
<Card className='space-y-6 px-6 py-12 text-center'>
|
||||
<Stack>
|
||||
<CardTitle title={<FormattedMessage id='thread_login.title' defaultMessage='Continue the conversation' />} />
|
||||
<Text>
|
||||
|
|
|
@ -44,7 +44,7 @@ const ThreadStatus: React.FC<IThreadStatus> = (props): JSX.Element => {
|
|||
// @ts-ignore FIXME
|
||||
<StatusContainer {...props} showGroup={false} />
|
||||
) : (
|
||||
<PlaceholderStatus variant='default' />
|
||||
<PlaceholderStatus slim />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -399,9 +399,7 @@ const Thread = (props: IThread) => {
|
|||
</div>
|
||||
</HotKeys>
|
||||
|
||||
{hasDescendants && (
|
||||
<hr className='-mx-4 mt-2 max-w-[100vw] border-t-2 black:border-t dark:border-gray-800' />
|
||||
)}
|
||||
<hr className='-mx-4 mt-2 max-w-[100vw] border-t-2 black:border-t dark:border-gray-800' />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -445,7 +443,7 @@ const Thread = (props: IThread) => {
|
|||
ref={scroller}
|
||||
hasMore={!!next}
|
||||
onLoadMore={handleLoadMore}
|
||||
placeholderComponent={() => <PlaceholderStatus variant='slim' />}
|
||||
placeholderComponent={() => <PlaceholderStatus />}
|
||||
initialTopMostItemIndex={initialTopMostItemIndex}
|
||||
useWindowScroll={useWindowScroll}
|
||||
itemClassName={itemClassName}
|
||||
|
|
|
@ -12,8 +12,6 @@ import { expandTimelineSuccess } from 'soapbox/actions/timelines.ts';
|
|||
import { Column } from 'soapbox/components/ui/column.tsx';
|
||||
import Timeline from 'soapbox/features/ui/components/timeline.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useTheme } from 'soapbox/hooks/useTheme.ts';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.test', defaultMessage: 'Test timeline' },
|
||||
|
@ -31,8 +29,6 @@ const onlyMedia = false;
|
|||
const TestTimeline: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(importFetchedStatuses(MOCK_STATUSES));
|
||||
|
@ -40,12 +36,11 @@ const TestTimeline: React.FC = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)} transparent={!isMobile}>
|
||||
<Column label={intl.formatMessage(messages.title)} slim>
|
||||
<Timeline
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
emptyMessage={<FormattedMessage id='empty_column.test' defaultMessage='The test timeline is empty.' />}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
interface IBackgroundShapes {
|
||||
/** Whether the shapes should be absolute positioned or fixed. */
|
||||
position?: 'fixed' | 'absolute';
|
||||
}
|
||||
|
||||
/** Gradient that appears in the background of the UI. */
|
||||
const BackgroundShapes: React.FC<IBackgroundShapes> = ({ position = 'fixed' }) => (
|
||||
<div className={clsx(position, 'pointer-events-none inset-x-0 top-0 flex justify-center overflow-hidden black:hidden')}>
|
||||
<div className='bg-gradient-sm lg:bg-gradient-light lg:dark:bg-gradient-dark h-screen w-screen' />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default BackgroundShapes;
|
|
@ -2,7 +2,7 @@ import { Card, CardBody } from 'soapbox/components/ui/card.tsx';
|
|||
import Spinner from 'soapbox/components/ui/spinner.tsx';
|
||||
|
||||
const ColumnLoading = () => (
|
||||
<Card variant='rounded'>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Spinner />
|
||||
</CardBody>
|
||||
|
|
|
@ -21,7 +21,6 @@ import Search from 'soapbox/features/compose/components/search.tsx';
|
|||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
|
||||
import { useRegistrationStatus } from 'soapbox/hooks/useRegistrationStatus.ts';
|
||||
import { useSettingsNotifications } from 'soapbox/hooks/useSettingsNotifications.ts';
|
||||
|
@ -44,7 +43,6 @@ const Navbar = () => {
|
|||
const { isOpen } = useRegistrationStatus();
|
||||
const { account } = useOwnAccount();
|
||||
const node = useRef(null);
|
||||
const isMobile = useIsMobile();
|
||||
const settingsNotifications = useSettingsNotifications();
|
||||
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
|
@ -87,10 +85,7 @@ const Navbar = () => {
|
|||
|
||||
return (
|
||||
<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 },
|
||||
)}
|
||||
className='border-b border-gray-200 bg-white shadow black:border-gray-800 black:bg-black dark:border-gray-800 dark:bg-primary-900'
|
||||
ref={node}
|
||||
data-testid='navbar'
|
||||
>
|
||||
|
|
|
@ -65,7 +65,6 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
|
|||
'py-6 sm:p-5': !thread,
|
||||
'status-reply': !!status.in_reply_to_id,
|
||||
})}
|
||||
variant={thread ? 'default' : 'rounded'}
|
||||
>
|
||||
<div className='mb-4'>
|
||||
<HStack justifyContent='between' alignItems='start'>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useFloating } from '@floating-ui/react';
|
||||
import { Placement, useFloating } from '@floating-ui/react';
|
||||
import logoutIcon from '@tabler/icons/outline/logout.svg';
|
||||
import plusIcon from '@tabler/icons/outline/plus.svg';
|
||||
import clsx from 'clsx';
|
||||
|
@ -29,6 +29,7 @@ const messages = defineMessages({
|
|||
interface IProfileDropdown {
|
||||
account: AccountEntity;
|
||||
children: React.ReactNode;
|
||||
placement?: Placement;
|
||||
}
|
||||
|
||||
type IMenuItem = {
|
||||
|
@ -39,13 +40,13 @@ type IMenuItem = {
|
|||
action?: (event: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
|
||||
const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children, placement = 'bottom-end' }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const intl = useIntl();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { x, y, strategy, refs } = useFloating<HTMLButtonElement>({ placement: 'bottom-end' });
|
||||
const { x, y, strategy, refs } = useFloating<HTMLButtonElement>({ placement });
|
||||
|
||||
const getOtherAccounts = useCallback(makeGetOtherAccounts(), []);
|
||||
const otherAccounts = useAppSelector((state) => getOtherAccounts(state));
|
||||
|
@ -117,7 +118,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
|
|||
return (
|
||||
<>
|
||||
<button
|
||||
className='rounded-full focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-gray-800 dark:ring-offset-0 dark:focus:ring-primary-500'
|
||||
className='w-full rounded-full focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-gray-800 dark:ring-offset-0 dark:focus:ring-primary-500'
|
||||
type='button'
|
||||
ref={refs.setReference}
|
||||
onClick={toggleVisible}
|
||||
|
|
|
@ -127,7 +127,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
|
|||
|
||||
if (!account) {
|
||||
return (
|
||||
<div className='mt-6 min-w-0 flex-1 sm:px-2'>
|
||||
<div>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<HStack space={1} alignItems='center'>
|
||||
|
@ -147,7 +147,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
|
|||
const badges = getBadges();
|
||||
|
||||
return (
|
||||
<div className='mt-6 min-w-0 flex-1 sm:px-2'>
|
||||
<div>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<HStack space={1} alignItems='center'>
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useAccount } from 'soapbox/api/hooks/index.ts';
|
||||
import StillImage from 'soapbox/components/still-image.tsx';
|
||||
import Avatar from 'soapbox/components/ui/avatar.tsx';
|
||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import VerificationBadge from 'soapbox/components/verification-badge.tsx';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { getAcct } from 'soapbox/utils/accounts.ts';
|
||||
import { emojifyText } from 'soapbox/utils/emojify.tsx';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers.tsx';
|
||||
import { displayFqn } from 'soapbox/utils/state.ts';
|
||||
|
||||
interface IUserPanel {
|
||||
accountId: string;
|
||||
action?: JSX.Element;
|
||||
badges?: JSX.Element[];
|
||||
domain?: string;
|
||||
}
|
||||
|
||||
const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain }) => {
|
||||
const intl = useIntl();
|
||||
const { account } = useAccount(accountId);
|
||||
const fqn = useAppSelector((state) => displayFqn(state));
|
||||
|
||||
if (!account) return null;
|
||||
const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct;
|
||||
const header = account.header;
|
||||
const verified = account.verified;
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<div className='relative -mx-4 -mt-4 h-24 overflow-hidden bg-gray-200'>
|
||||
{header && (
|
||||
<StillImage src={account.header} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<HStack justifyContent='between'>
|
||||
<Link
|
||||
to={`/@${account.acct}`}
|
||||
title={acct}
|
||||
className='-mt-12 block'
|
||||
>
|
||||
<Avatar src={account.avatar} size={80} className='size-20 overflow-hidden bg-gray-50 ring-2 ring-white' />
|
||||
</Link>
|
||||
|
||||
{action && (
|
||||
<div className='mt-2'>{action}</div>
|
||||
)}
|
||||
</HStack>
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Link to={`/@${account.acct}`}>
|
||||
<HStack space={1} alignItems='center'>
|
||||
<Text size='lg' weight='bold' truncate>
|
||||
{emojifyText(account.display_name, account.emojis)}
|
||||
</Text>
|
||||
|
||||
{verified && <VerificationBadge />}
|
||||
|
||||
{badges && badges.length > 0 && (
|
||||
<HStack space={1} alignItems='center'>
|
||||
{badges}
|
||||
</HStack>
|
||||
)}
|
||||
</HStack>
|
||||
</Link>
|
||||
|
||||
<HStack>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<Text size='sm' theme='muted' direction='ltr' truncate>
|
||||
@{getAcct(account, fqn)}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Stack>
|
||||
|
||||
<HStack alignItems='center' space={3}>
|
||||
{account.followers_count >= 0 && (
|
||||
<Link to={`/@${account.acct}/followers`} title={intl.formatNumber(account.followers_count)}>
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Text theme='primary' weight='bold' size='sm'>
|
||||
{shortNumberFormat(account.followers_count)}
|
||||
</Text>
|
||||
<Text weight='bold' size='sm'>
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{account.following_count >= 0 && (
|
||||
<Link to={`/@${account.acct}/following`} title={intl.formatNumber(account.following_count)}>
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Text theme='primary' weight='bold' size='sm'>
|
||||
{shortNumberFormat(account.following_count)}
|
||||
</Text>
|
||||
<Text weight='bold' size='sm'>
|
||||
<FormattedMessage id='account.follows' defaultMessage='Following' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
)}
|
||||
</HStack>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPanel;
|
|
@ -43,7 +43,6 @@ import SearchPage from 'soapbox/pages/search-page.tsx';
|
|||
import StatusPage from 'soapbox/pages/status-page.tsx';
|
||||
import WidePage from 'soapbox/pages/wide-page.tsx';
|
||||
|
||||
import BackgroundShapes from './components/background-shapes.tsx';
|
||||
import FloatingActionButton from './components/floating-action-button.tsx';
|
||||
import Navbar from './components/navbar.tsx';
|
||||
import {
|
||||
|
@ -505,10 +504,10 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
})}
|
||||
/>
|
||||
|
||||
<BackgroundShapes />
|
||||
|
||||
<div className='z-10 flex min-h-screen flex-col'>
|
||||
<Navbar />
|
||||
<div className='sticky top-0 z-50 sm:hidden'>
|
||||
<Navbar />
|
||||
</div>
|
||||
|
||||
<Layout>
|
||||
<Layout.Sidebar>
|
||||
|
|
|
@ -82,7 +82,6 @@ export const ServerInfo = lazy(() => import('soapbox/features/server-info/index.
|
|||
export const Dashboard = lazy(() => import('soapbox/features/admin/index.tsx'));
|
||||
export const ModerationLog = lazy(() => import('soapbox/features/admin/moderation-log.tsx'));
|
||||
export const ThemeEditor = lazy(() => import('soapbox/features/theme-editor/index.tsx'));
|
||||
export const UserPanel = lazy(() => import('soapbox/features/ui/components/user-panel.tsx'));
|
||||
export const PromoPanel = lazy(() => import('soapbox/features/ui/components/promo-panel.tsx'));
|
||||
export const SignUpPanel = lazy(() => import('soapbox/features/ui/components/panels/sign-up-panel.tsx'));
|
||||
export const CtaBanner = lazy(() => import('soapbox/features/ui/components/cta-banner.tsx'));
|
||||
|
|
|
@ -25,7 +25,7 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
|
|||
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
|
||||
const dsn = soapboxConfig.sentryDsn;
|
||||
|
||||
const bodyClass = clsx('h-full bg-white text-base black:bg-black dark:bg-gray-800', {
|
||||
const bodyClass = clsx('h-full bg-white text-base black:bg-black dark:bg-primary-900', {
|
||||
'no-reduce-motion': !reduceMotion,
|
||||
'underline-links': underlineLinks,
|
||||
'demetricator': demetricator,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import clsx from 'clsx';
|
||||
import { useRef } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import { uploadCompose } from 'soapbox/actions/compose.ts';
|
||||
import Avatar from 'soapbox/components/ui/avatar.tsx';
|
||||
import { Card, CardBody } from 'soapbox/components/ui/card.tsx';
|
||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||
import Layout from 'soapbox/components/ui/layout.tsx';
|
||||
import Tabs from 'soapbox/components/ui/tabs.tsx';
|
||||
import LinkFooter from 'soapbox/features/ui/components/link-footer.tsx';
|
||||
import {
|
||||
WhoToFollowPanel,
|
||||
|
@ -24,11 +25,11 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
|||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useDraggedFiles } from 'soapbox/hooks/useDraggedFiles.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
|
||||
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
|
||||
|
||||
|
||||
import ComposeForm from '../features/compose/components/compose-form.tsx';
|
||||
|
||||
interface IHomePage {
|
||||
|
@ -38,11 +39,13 @@ interface IHomePage {
|
|||
const HomePage: React.FC<IHomePage> = ({ children }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const me = useAppSelector(state => state.me);
|
||||
const { account } = useOwnAccount();
|
||||
const features = useFeatures();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const { instance } = useInstance();
|
||||
|
||||
const composeId = 'home';
|
||||
const composeBlock = useRef<HTMLDivElement>(null);
|
||||
|
@ -61,15 +64,14 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Layout.Main className={clsx('black:space-y-0 dark:divide-gray-800', { 'pt-3 sm:pt-0 space-y-3': !isMobile })}>
|
||||
<Layout.Main className={clsx('space-y-0 dark:divide-gray-800')}>
|
||||
{me && (
|
||||
<Card
|
||||
className={clsx('relative z-[1] border-gray-200 transition black:border-b black:border-gray-800 dark:border-gray-800', {
|
||||
className={clsx('relative z-[1] border-b border-gray-200 transition black:border-gray-800 dark:border-gray-800', {
|
||||
'border-2 border-primary-600 border-dashed z-[99]': isDragging,
|
||||
'ring-2 ring-offset-2 ring-primary-600': isDraggedOver,
|
||||
'border-b': isMobile,
|
||||
})}
|
||||
variant='rounded'
|
||||
ref={composeBlock}
|
||||
>
|
||||
<CardBody>
|
||||
|
@ -91,6 +93,16 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
|
|||
</Card>
|
||||
)}
|
||||
|
||||
<div className='sticky top-12 z-20 bg-white/90 backdrop-blur black:bg-black/90 dark:bg-primary-900/90 lg:top-0'>
|
||||
<Tabs
|
||||
items={[
|
||||
{ name: 'home', text: <FormattedMessage id='tabs_bar.home' defaultMessage='Home' />, to: '/' },
|
||||
{ name: 'local', text: <div className='block max-w-xs truncate'>{instance.domain}</div>, to: '/timeline/local' },
|
||||
]}
|
||||
activeItem={pathname === '/timeline/local' ? 'local' : 'home'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
|
||||
{!me && (
|
||||
|
@ -105,22 +117,22 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
|
|||
{me && features.announcements && (
|
||||
<AnnouncementsPanel />
|
||||
)}
|
||||
{(hasCrypto && cryptoLimit > 0 && me) && (
|
||||
<CryptoDonatePanel limit={cryptoLimit} />
|
||||
)}
|
||||
{(hasPatron && me) && (
|
||||
<FundingPanel />
|
||||
)}
|
||||
{features.birthdays && (
|
||||
<BirthdayPanel limit={10} />
|
||||
)}
|
||||
{features.trends && (
|
||||
<TrendsPanel limit={5} />
|
||||
)}
|
||||
{features.suggestions && (
|
||||
<WhoToFollowPanel limit={3} />
|
||||
)}
|
||||
{features.birthdays && (
|
||||
<BirthdayPanel limit={10} />
|
||||
)}
|
||||
<PromoPanel />
|
||||
{(hasCrypto && cryptoLimit > 0 && me) && (
|
||||
<CryptoDonatePanel limit={cryptoLimit} />
|
||||
)}
|
||||
{(hasPatron && me) && (
|
||||
<FundingPanel />
|
||||
)}
|
||||
<LinkFooter />
|
||||
</Layout.Aside>
|
||||
</>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Redirect, useHistory } from 'react-router-dom';
|
|||
import { useAccountLookup } from 'soapbox/api/hooks/index.ts';
|
||||
import { Column } from 'soapbox/components/ui/column.tsx';
|
||||
import Layout from 'soapbox/components/ui/layout.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Tabs from 'soapbox/components/ui/tabs.tsx';
|
||||
import Header from 'soapbox/features/account/components/header.tsx';
|
||||
import LinkFooter from 'soapbox/features/ui/components/link-footer.tsx';
|
||||
|
@ -91,17 +92,20 @@ const ProfilePage: React.FC<IProfilePage> = ({ params, children }) => {
|
|||
return (
|
||||
<>
|
||||
<Layout.Main>
|
||||
<Column size='lg' label={account ? `@${getAcct(account, displayFqn)}` : ''} withHeader={false}>
|
||||
<div className='space-y-4'>
|
||||
<Column size='lg' label={account ? `@${getAcct(account, displayFqn)}` : ''} withHeader={false} slim>
|
||||
<Stack space={4}>
|
||||
<Header account={account} />
|
||||
<ProfileInfoPanel username={username} account={account} />
|
||||
|
||||
{account && showTabs && (
|
||||
<Tabs key={`profile-tabs-${account.id}`} items={tabItems} activeItem={activeItem} />
|
||||
)}
|
||||
<Stack space={4} className='px-6'>
|
||||
<ProfileInfoPanel username={username} account={account} />
|
||||
|
||||
{account && showTabs && (
|
||||
<Tabs key={`profile-tabs-${account.id}`} items={tabItems} activeItem={activeItem} />
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</Stack>
|
||||
</Column>
|
||||
|
||||
{!me && (
|
||||
|
|
Ładowanie…
Reference in New Issue