mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Fixed separator showing even after the last list item (#21620)
close https://linear.app/ghost/issue/AP-528/remove-unnecessary-separator-after-last-list-items - Previously, in some components, we relied on CSS and all elements in the list being the same type of an HTML element to show the separator. Now, we've extracted the separator into its own component and place after all items during mapping except the last one.
This commit is contained in:
parent
0aae3bb1fc
commit
af54b21bf3
8 changed files with 118 additions and 95 deletions
|
@ -8,6 +8,7 @@ import APAvatar, {AvatarBadge} from './global/APAvatar';
|
||||||
import ActivityItem, {type Activity} from './activities/ActivityItem';
|
import ActivityItem, {type Activity} from './activities/ActivityItem';
|
||||||
import ArticleModal from './feed/ArticleModal';
|
import ArticleModal from './feed/ArticleModal';
|
||||||
import MainNavigation from './navigation/MainNavigation';
|
import MainNavigation from './navigation/MainNavigation';
|
||||||
|
import Separator from './global/Separator';
|
||||||
import ViewProfileModal from './global/ViewProfileModal';
|
import ViewProfileModal from './global/ViewProfileModal';
|
||||||
|
|
||||||
import getUsername from '../utils/get-username';
|
import getUsername from '../utils/get-username';
|
||||||
|
@ -184,9 +185,9 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
||||||
(isLoading === false && activities.length > 0) && (
|
(isLoading === false && activities.length > 0) && (
|
||||||
<>
|
<>
|
||||||
<div className='mt-8 flex w-full max-w-[560px] flex-col'>
|
<div className='mt-8 flex w-full max-w-[560px] flex-col'>
|
||||||
{activities?.map(activity => (
|
{activities?.map((activity, index) => (
|
||||||
|
<React.Fragment key={activity.id}>
|
||||||
<ActivityItem
|
<ActivityItem
|
||||||
key={activity.id}
|
|
||||||
url={getActivityUrl(activity) || getActorUrl(activity)}
|
url={getActivityUrl(activity) || getActorUrl(activity)}
|
||||||
onClick={() => handleActivityClick(activity)}
|
onClick={() => handleActivityClick(activity)}
|
||||||
>
|
>
|
||||||
|
@ -199,13 +200,9 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
||||||
<div className=''>{getActivityDescription(activity)}</div>
|
<div className=''>{getActivityDescription(activity)}</div>
|
||||||
{getExtendedDescription(activity)}
|
{getExtendedDescription(activity)}
|
||||||
</div>
|
</div>
|
||||||
{/* <FollowButton
|
|
||||||
className='ml-auto'
|
|
||||||
following={isFollower(activity.actor.id)}
|
|
||||||
handle={getUsername(activity.actor)}
|
|
||||||
type='link'
|
|
||||||
/> */}
|
|
||||||
</ActivityItem>
|
</ActivityItem>
|
||||||
|
{index < activities.length - 1 && <Separator />}
|
||||||
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div ref={loadMoreRef} className='h-1'></div>
|
<div ref={loadMoreRef} className='h-1'></div>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import FeedItem from './feed/FeedItem';
|
||||||
import MainNavigation from './navigation/MainNavigation';
|
import MainNavigation from './navigation/MainNavigation';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
import React, {useEffect, useRef} from 'react';
|
import React, {useEffect, useRef} from 'react';
|
||||||
|
import Separator from './global/Separator';
|
||||||
import ViewProfileModal from './global/ViewProfileModal';
|
import ViewProfileModal from './global/ViewProfileModal';
|
||||||
import getName from '../utils/get-name';
|
import getName from '../utils/get-name';
|
||||||
import getUsername from '../utils/get-username';
|
import getUsername from '../utils/get-username';
|
||||||
|
@ -94,7 +95,7 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
onCommentClick={() => handleViewContent(activity, true, updateActivity)}
|
onCommentClick={() => handleViewContent(activity, true, updateActivity)}
|
||||||
/>
|
/>
|
||||||
{index < activities.length - 1 && (
|
{index < activities.length - 1 && (
|
||||||
<div className="h-px w-full bg-grey-200"></div>
|
<Separator />
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -107,15 +108,16 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className='sticky top-[135px] ml-auto w-full max-w-[300px] max-lg:hidden xxxl:sticky xxxl:right-[40px]'>
|
<div className='sticky top-[135px] ml-auto w-full max-w-[300px] max-lg:hidden xxxl:sticky xxxl:right-[40px]'>
|
||||||
<h2 className='mb-2 text-lg font-semibold'>You might also like...</h2>
|
<h2 className='mb-2 text-lg font-semibold'>You might also like</h2>
|
||||||
{isLoadingSuggested ? (
|
{isLoadingSuggested ? (
|
||||||
<LoadingIndicator size="sm" />
|
<LoadingIndicator size="sm" />
|
||||||
) : (
|
) : (
|
||||||
<ul className='grow'>
|
<ul className='grow'>
|
||||||
{suggested.map((profile) => {
|
{suggested.map((profile, index) => {
|
||||||
const actor = profile.actor;
|
const actor = profile.actor;
|
||||||
// const isFollowing = profile.isFollowing;
|
// const isFollowing = profile.isFollowing;
|
||||||
return (
|
return (
|
||||||
|
<React.Fragment key={actor.id}>
|
||||||
<li key={actor.id}>
|
<li key={actor.id}>
|
||||||
<ActivityItem url={actor.url} onClick={() => NiceModal.show(ViewProfileModal, {
|
<ActivityItem url={actor.url} onClick={() => NiceModal.show(ViewProfileModal, {
|
||||||
profile: getUsername(actor),
|
profile: getUsername(actor),
|
||||||
|
@ -123,11 +125,9 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
onUnfollow: () => {}
|
onUnfollow: () => {}
|
||||||
})}>
|
})}>
|
||||||
<APAvatar author={actor} />
|
<APAvatar author={actor} />
|
||||||
<div>
|
<div className='flex min-w-0 flex-col'>
|
||||||
<div className='text-grey-600'>
|
<span className='block w-full truncate font-bold text-black'>{getName(actor)}</span>
|
||||||
<span className='mr-1 truncate font-bold text-black'>{getName(actor)}</span>
|
<span className='block w-full truncate text-sm text-grey-600'>{getUsername(actor)}</span>
|
||||||
<div className='truncate text-sm'>{getUsername(actor)}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* <FollowButton
|
{/* <FollowButton
|
||||||
className='ml-auto'
|
className='ml-auto'
|
||||||
|
@ -139,6 +139,8 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
/> */}
|
/> */}
|
||||||
</ActivityItem>
|
</ActivityItem>
|
||||||
</li>
|
</li>
|
||||||
|
{index < suggested.length - 1 && <Separator />}
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import getName from '../utils/get-name';
|
||||||
import getUsername from '../utils/get-username';
|
import getUsername from '../utils/get-username';
|
||||||
import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||||
|
|
||||||
|
import Separator from './global/Separator';
|
||||||
import ViewProfileModal from './global/ViewProfileModal';
|
import ViewProfileModal from './global/ViewProfileModal';
|
||||||
import {Button, Heading, List, NoValueLabel, Tab, TabView} from '@tryghost/admin-x-design-system';
|
import {Button, Heading, List, NoValueLabel, Tab, TabView} from '@tryghost/admin-x-design-system';
|
||||||
import {handleViewContent} from '../utils/content-handlers';
|
import {handleViewContent} from '../utils/content-handlers';
|
||||||
|
@ -102,7 +103,7 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
||||||
onCommentClick={() => handleViewContent(activity, true)}
|
onCommentClick={() => handleViewContent(activity, true)}
|
||||||
/>
|
/>
|
||||||
{index < posts.length - 1 && (
|
{index < posts.length - 1 && (
|
||||||
<div className="h-px w-full bg-grey-200"></div>
|
<Separator />
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -147,7 +148,7 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
||||||
onCommentClick={() => handleViewContent(activity, true)}
|
onCommentClick={() => handleViewContent(activity, true)}
|
||||||
/>
|
/>
|
||||||
{index < liked.length - 1 && (
|
{index < liked.length - 1 && (
|
||||||
<div className="h-px w-full bg-grey-200"></div>
|
<Separator />
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -178,8 +179,9 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
||||||
</NoValueLabel>
|
</NoValueLabel>
|
||||||
) : (
|
) : (
|
||||||
<List>
|
<List>
|
||||||
{following.slice(0, visibleFollowing).map((item) => {
|
{following.slice(0, visibleFollowing).map((item, index) => {
|
||||||
return (
|
return (
|
||||||
|
<React.Fragment key={item.id}>
|
||||||
<ActivityItem
|
<ActivityItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
url={item.url}
|
url={item.url}
|
||||||
|
@ -197,6 +199,8 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
||||||
alert('Implement me!');
|
alert('Implement me!');
|
||||||
}} /> */}
|
}} /> */}
|
||||||
</ActivityItem>
|
</ActivityItem>
|
||||||
|
{index < following.length - 1 && <Separator />}
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
|
@ -226,8 +230,9 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
||||||
</NoValueLabel>
|
</NoValueLabel>
|
||||||
) : (
|
) : (
|
||||||
<List>
|
<List>
|
||||||
{followers.slice(0, visibleFollowers).map((item) => {
|
{followers.slice(0, visibleFollowers).map((item, index) => {
|
||||||
return (
|
return (
|
||||||
|
<React.Fragment key={item.id}>
|
||||||
<ActivityItem
|
<ActivityItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
url={item.url}
|
url={item.url}
|
||||||
|
@ -241,6 +246,8 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ActivityItem>
|
</ActivityItem>
|
||||||
|
{index < followers.length - 1 && <Separator />}
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import MainNavigation from './navigation/MainNavigation';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
import ViewProfileModal from './global/ViewProfileModal';
|
import ViewProfileModal from './global/ViewProfileModal';
|
||||||
|
|
||||||
|
import Separator from './global/Separator';
|
||||||
import {useSearchForUser, useSuggestedProfiles} from '../hooks/useActivityPubQueries';
|
import {useSearchForUser, useSuggestedProfiles} from '../hooks/useActivityPubQueries';
|
||||||
|
|
||||||
interface SearchResultItem {
|
interface SearchResultItem {
|
||||||
|
@ -103,13 +104,18 @@ const SuggestedAccounts: React.FC<{
|
||||||
<LoadingIndicator size='md'/>
|
<LoadingIndicator size='md'/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{profiles.map(profile => (
|
{profiles.map((profile, index) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={profile.actor.id}>
|
||||||
<SearchResult
|
<SearchResult
|
||||||
key={profile.actor.id}
|
key={profile.actor.id}
|
||||||
result={profile}
|
result={profile}
|
||||||
update={onUpdate}
|
update={onUpdate}
|
||||||
/>
|
/>
|
||||||
))}
|
{index < profiles.length - 1 && <Separator />}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -141,8 +147,8 @@ const Search: React.FC<SearchProps> = ({}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainNavigation page='search' />
|
<MainNavigation page='search' />
|
||||||
<div className='z-0 flex w-full flex-col items-center pt-8'>
|
<div className='z-0 mx-auto flex w-full max-w-[560px] flex-col items-center pt-8'>
|
||||||
<div className='relative flex w-full max-w-[560px] items-center '>
|
<div className='relative flex w-full items-center'>
|
||||||
<Icon className='absolute left-3 top-3 z-10' colorClass='text-grey-500' name='magnifying-glass' size='sm' />
|
<Icon className='absolute left-3 top-3 z-10' colorClass='text-grey-500' name='magnifying-glass' size='sm' />
|
||||||
<TextField
|
<TextField
|
||||||
className='mb-6 mr-12 flex h-10 w-full items-center rounded-lg border border-transparent bg-grey-100 px-[33px] py-1.5 transition-colors focus:border-green focus:bg-white focus:outline-2 dark:border-transparent dark:bg-grey-925 dark:text-white dark:placeholder:text-grey-800 dark:focus:border-green dark:focus:bg-grey-950 tablet:mr-0'
|
className='mb-6 mr-12 flex h-10 w-full items-center rounded-lg border border-transparent bg-grey-100 px-[33px] py-1.5 transition-colors focus:border-green focus:bg-white focus:outline-2 dark:border-transparent dark:bg-grey-925 dark:text-white dark:placeholder:text-grey-800 dark:focus:border-green dark:focus:bg-grey-950 tablet:mr-0'
|
||||||
|
|
|
@ -23,7 +23,7 @@ const ActivityItem: React.FC<ActivityItemProps> = ({children, url = null, onClic
|
||||||
const childrenArray = React.Children.toArray(children);
|
const childrenArray = React.Children.toArray(children);
|
||||||
|
|
||||||
const Item = (
|
const Item = (
|
||||||
<div className='relative flex w-full max-w-[560px] cursor-pointer flex-col border-b border-grey-100 before:absolute before:inset-x-[-8px] before:inset-y-[-1px] before:rounded-md before:bg-grey-50 before:opacity-0 before:transition-opacity hover:z-10 hover:cursor-pointer hover:border-b-transparent hover:before:opacity-100 dark:border-grey-950 dark:before:bg-grey-950' onClick={() => {
|
<div className='relative flex w-full max-w-[560px] cursor-pointer flex-col before:absolute before:inset-x-[-8px] before:inset-y-[-1px] before:rounded-md before:bg-grey-50 before:opacity-0 before:transition-opacity hover:z-10 hover:cursor-pointer hover:border-b-transparent hover:before:opacity-100 dark:before:bg-grey-950' onClick={() => {
|
||||||
if (!url && onClick) {
|
if (!url && onClick) {
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Separator: React.FC = () => {
|
||||||
|
return <div className='h-px w-full bg-grey-150 dark:bg-grey-950' />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Separator;
|
|
@ -13,6 +13,7 @@ import APAvatar from '../global/APAvatar';
|
||||||
import ActivityItem from '../activities/ActivityItem';
|
import ActivityItem from '../activities/ActivityItem';
|
||||||
import FeedItem from '../feed/FeedItem';
|
import FeedItem from '../feed/FeedItem';
|
||||||
import FollowButton from '../global/FollowButton';
|
import FollowButton from '../global/FollowButton';
|
||||||
|
import Separator from './Separator';
|
||||||
import getName from '../../utils/get-name';
|
import getName from '../../utils/get-name';
|
||||||
import getUsername from '../../utils/get-username';
|
import getUsername from '../../utils/get-username';
|
||||||
|
|
||||||
|
@ -81,8 +82,9 @@ const ActorList: React.FC<ActorListProps> = ({
|
||||||
</NoValueLabel>
|
</NoValueLabel>
|
||||||
) : (
|
) : (
|
||||||
<List>
|
<List>
|
||||||
{actorData.map(({actor, isFollowing}) => {
|
{actorData.map(({actor, isFollowing}, index) => {
|
||||||
return (
|
return (
|
||||||
|
<React.Fragment key={actor.id}>
|
||||||
<ActivityItem key={actor.id} url={actor.url}>
|
<ActivityItem key={actor.id} url={actor.url}>
|
||||||
<APAvatar author={actor} />
|
<APAvatar author={actor} />
|
||||||
<div>
|
<div>
|
||||||
|
@ -98,6 +100,8 @@ const ActorList: React.FC<ActorListProps> = ({
|
||||||
type='link'
|
type='link'
|
||||||
/>
|
/>
|
||||||
</ActivityItem>
|
</ActivityItem>
|
||||||
|
{index < actorData.length - 1 && <Separator />}
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
|
@ -188,7 +192,7 @@ const ViewProfileModal: React.FC<ViewProfileModalProps> = ({
|
||||||
onCommentClick={() => {}}
|
onCommentClick={() => {}}
|
||||||
/>
|
/>
|
||||||
{index < posts.length - 1 && (
|
{index < posts.length - 1 && (
|
||||||
<div className="h-px w-full bg-grey-200"></div>
|
<Separator />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -29,7 +29,7 @@ const MainNavigation: React.FC<MainNavigationProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div className='col-[3/4] flex items-center justify-end gap-2 px-8'>
|
<div className='col-[3/4] flex items-center justify-end gap-2 px-8'>
|
||||||
{page === 'home' &&
|
{page === 'home' &&
|
||||||
<div className='mr-3'>
|
<div>
|
||||||
<Tooltip content="Inbox">
|
<Tooltip content="Inbox">
|
||||||
<Button className='!px-2' icon='listview' iconColorClass={layout === 'inbox' ? 'text-black' : 'text-grey-400'} size='sm' onClick={setInbox} />
|
<Button className='!px-2' icon='listview' iconColorClass={layout === 'inbox' ? 'text-black' : 'text-grey-400'} size='sm' onClick={setInbox} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
Loading…
Add table
Reference in a new issue