mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -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 ArticleModal from './feed/ArticleModal';
|
||||
import MainNavigation from './navigation/MainNavigation';
|
||||
import Separator from './global/Separator';
|
||||
import ViewProfileModal from './global/ViewProfileModal';
|
||||
|
||||
import getUsername from '../utils/get-username';
|
||||
|
@ -184,28 +185,24 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
(isLoading === false && activities.length > 0) && (
|
||||
<>
|
||||
<div className='mt-8 flex w-full max-w-[560px] flex-col'>
|
||||
{activities?.map(activity => (
|
||||
<ActivityItem
|
||||
key={activity.id}
|
||||
url={getActivityUrl(activity) || getActorUrl(activity)}
|
||||
onClick={() => handleActivityClick(activity)}
|
||||
>
|
||||
<APAvatar author={activity.actor} badge={getActivityBadge(activity)} />
|
||||
<div className='min-w-0'>
|
||||
<div className='truncate text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{activity.actor.name}</span>
|
||||
{getUsername(activity.actor)}
|
||||
{activities?.map((activity, index) => (
|
||||
<React.Fragment key={activity.id}>
|
||||
<ActivityItem
|
||||
url={getActivityUrl(activity) || getActorUrl(activity)}
|
||||
onClick={() => handleActivityClick(activity)}
|
||||
>
|
||||
<APAvatar author={activity.actor} badge={getActivityBadge(activity)} />
|
||||
<div className='min-w-0'>
|
||||
<div className='truncate text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{activity.actor.name}</span>
|
||||
{getUsername(activity.actor)}
|
||||
</div>
|
||||
<div className=''>{getActivityDescription(activity)}</div>
|
||||
{getExtendedDescription(activity)}
|
||||
</div>
|
||||
<div className=''>{getActivityDescription(activity)}</div>
|
||||
{getExtendedDescription(activity)}
|
||||
</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 ref={loadMoreRef} className='h-1'></div>
|
||||
|
|
|
@ -5,6 +5,7 @@ import FeedItem from './feed/FeedItem';
|
|||
import MainNavigation from './navigation/MainNavigation';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import Separator from './global/Separator';
|
||||
import ViewProfileModal from './global/ViewProfileModal';
|
||||
import getName from '../utils/get-name';
|
||||
import getUsername from '../utils/get-username';
|
||||
|
@ -94,7 +95,7 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
|||
onCommentClick={() => handleViewContent(activity, true, updateActivity)}
|
||||
/>
|
||||
{index < activities.length - 1 && (
|
||||
<div className="h-px w-full bg-grey-200"></div>
|
||||
<Separator />
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
@ -107,29 +108,28 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
|||
</ul>
|
||||
</div>
|
||||
<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 ? (
|
||||
<LoadingIndicator size="sm" />
|
||||
) : (
|
||||
<ul className='grow'>
|
||||
{suggested.map((profile) => {
|
||||
{suggested.map((profile, index) => {
|
||||
const actor = profile.actor;
|
||||
// const isFollowing = profile.isFollowing;
|
||||
return (
|
||||
<li key={actor.id}>
|
||||
<ActivityItem url={actor.url} onClick={() => NiceModal.show(ViewProfileModal, {
|
||||
profile: getUsername(actor),
|
||||
onFollow: () => {},
|
||||
onUnfollow: () => {}
|
||||
})}>
|
||||
<APAvatar author={actor} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 truncate font-bold text-black'>{getName(actor)}</span>
|
||||
<div className='truncate text-sm'>{getUsername(actor)}</div>
|
||||
<React.Fragment key={actor.id}>
|
||||
<li key={actor.id}>
|
||||
<ActivityItem url={actor.url} onClick={() => NiceModal.show(ViewProfileModal, {
|
||||
profile: getUsername(actor),
|
||||
onFollow: () => {},
|
||||
onUnfollow: () => {}
|
||||
})}>
|
||||
<APAvatar author={actor} />
|
||||
<div className='flex min-w-0 flex-col'>
|
||||
<span className='block w-full truncate font-bold text-black'>{getName(actor)}</span>
|
||||
<span className='block w-full truncate text-sm text-grey-600'>{getUsername(actor)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* <FollowButton
|
||||
{/* <FollowButton
|
||||
className='ml-auto'
|
||||
following={isFollowing}
|
||||
handle={getUsername(actor)}
|
||||
|
@ -137,8 +137,10 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
|||
onFollow={() => updateSuggestedProfile(actor.id!, {isFollowing: true})}
|
||||
onUnfollow={() => updateSuggestedProfile(actor.id!, {isFollowing: false})}
|
||||
/> */}
|
||||
</ActivityItem>
|
||||
</li>
|
||||
</ActivityItem>
|
||||
</li>
|
||||
{index < suggested.length - 1 && <Separator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
|
|
@ -8,6 +8,7 @@ import getName from '../utils/get-name';
|
|||
import getUsername from '../utils/get-username';
|
||||
import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
|
||||
import Separator from './global/Separator';
|
||||
import ViewProfileModal from './global/ViewProfileModal';
|
||||
import {Button, Heading, List, NoValueLabel, Tab, TabView} from '@tryghost/admin-x-design-system';
|
||||
import {handleViewContent} from '../utils/content-handlers';
|
||||
|
@ -102,7 +103,7 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
|||
onCommentClick={() => handleViewContent(activity, true)}
|
||||
/>
|
||||
{index < posts.length - 1 && (
|
||||
<div className="h-px w-full bg-grey-200"></div>
|
||||
<Separator />
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
@ -147,7 +148,7 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
|||
onCommentClick={() => handleViewContent(activity, true)}
|
||||
/>
|
||||
{index < liked.length - 1 && (
|
||||
<div className="h-px w-full bg-grey-200"></div>
|
||||
<Separator />
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
@ -178,25 +179,28 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
|||
</NoValueLabel>
|
||||
) : (
|
||||
<List>
|
||||
{following.slice(0, visibleFollowing).map((item) => {
|
||||
{following.slice(0, visibleFollowing).map((item, index) => {
|
||||
return (
|
||||
<ActivityItem
|
||||
key={item.id}
|
||||
url={item.url}
|
||||
onClick={() => handleUserClick(item)}
|
||||
>
|
||||
<APAvatar author={item} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{getName(item)}</span>
|
||||
<div className='text-sm'>{getUsername(item)}</div>
|
||||
<React.Fragment key={item.id}>
|
||||
<ActivityItem
|
||||
key={item.id}
|
||||
url={item.url}
|
||||
onClick={() => handleUserClick(item)}
|
||||
>
|
||||
<APAvatar author={item} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{getName(item)}</span>
|
||||
<div className='text-sm'>{getUsername(item)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Button className='ml-auto' color='grey' label='Unfollow' link={true} onClick={(e) => {
|
||||
{/* <Button className='ml-auto' color='grey' label='Unfollow' link={true} onClick={(e) => {
|
||||
e?.preventDefault();
|
||||
alert('Implement me!');
|
||||
}} /> */}
|
||||
</ActivityItem>
|
||||
</ActivityItem>
|
||||
{index < following.length - 1 && <Separator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
@ -226,21 +230,24 @@ const Profile: React.FC<ProfileProps> = ({}) => {
|
|||
</NoValueLabel>
|
||||
) : (
|
||||
<List>
|
||||
{followers.slice(0, visibleFollowers).map((item) => {
|
||||
{followers.slice(0, visibleFollowers).map((item, index) => {
|
||||
return (
|
||||
<ActivityItem
|
||||
key={item.id}
|
||||
url={item.url}
|
||||
onClick={() => handleUserClick(item)}
|
||||
>
|
||||
<APAvatar author={item} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{item.name || getName(item) || 'Unknown'}</span>
|
||||
<div className='text-sm'>{getUsername(item)}</div>
|
||||
<React.Fragment key={item.id}>
|
||||
<ActivityItem
|
||||
key={item.id}
|
||||
url={item.url}
|
||||
onClick={() => handleUserClick(item)}
|
||||
>
|
||||
<APAvatar author={item} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{item.name || getName(item) || 'Unknown'}</span>
|
||||
<div className='text-sm'>{getUsername(item)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ActivityItem>
|
||||
</ActivityItem>
|
||||
{index < followers.length - 1 && <Separator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
|
|
@ -12,6 +12,7 @@ import MainNavigation from './navigation/MainNavigation';
|
|||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import ViewProfileModal from './global/ViewProfileModal';
|
||||
|
||||
import Separator from './global/Separator';
|
||||
import {useSearchForUser, useSuggestedProfiles} from '../hooks/useActivityPubQueries';
|
||||
|
||||
interface SearchResultItem {
|
||||
|
@ -103,13 +104,18 @@ const SuggestedAccounts: React.FC<{
|
|||
<LoadingIndicator size='md'/>
|
||||
</div>
|
||||
)}
|
||||
{profiles.map(profile => (
|
||||
<SearchResult
|
||||
key={profile.actor.id}
|
||||
result={profile}
|
||||
update={onUpdate}
|
||||
/>
|
||||
))}
|
||||
{profiles.map((profile, index) => {
|
||||
return (
|
||||
<React.Fragment key={profile.actor.id}>
|
||||
<SearchResult
|
||||
key={profile.actor.id}
|
||||
result={profile}
|
||||
update={onUpdate}
|
||||
/>
|
||||
{index < profiles.length - 1 && <Separator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -141,8 +147,8 @@ const Search: React.FC<SearchProps> = ({}) => {
|
|||
return (
|
||||
<>
|
||||
<MainNavigation page='search' />
|
||||
<div className='z-0 flex w-full flex-col items-center pt-8'>
|
||||
<div className='relative flex w-full max-w-[560px] items-center '>
|
||||
<div className='z-0 mx-auto flex w-full max-w-[560px] flex-col items-center pt-8'>
|
||||
<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' />
|
||||
<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'
|
||||
|
|
|
@ -23,7 +23,7 @@ const ActivityItem: React.FC<ActivityItemProps> = ({children, url = null, onClic
|
|||
const childrenArray = React.Children.toArray(children);
|
||||
|
||||
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) {
|
||||
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 FeedItem from '../feed/FeedItem';
|
||||
import FollowButton from '../global/FollowButton';
|
||||
import Separator from './Separator';
|
||||
import getName from '../../utils/get-name';
|
||||
import getUsername from '../../utils/get-username';
|
||||
|
||||
|
@ -81,23 +82,26 @@ const ActorList: React.FC<ActorListProps> = ({
|
|||
</NoValueLabel>
|
||||
) : (
|
||||
<List>
|
||||
{actorData.map(({actor, isFollowing}) => {
|
||||
{actorData.map(({actor, isFollowing}, index) => {
|
||||
return (
|
||||
<ActivityItem key={actor.id} url={actor.url}>
|
||||
<APAvatar author={actor} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{getName(actor)}</span>
|
||||
<div className='text-sm'>{getUsername(actor)}</div>
|
||||
<React.Fragment key={actor.id}>
|
||||
<ActivityItem key={actor.id} url={actor.url}>
|
||||
<APAvatar author={actor} />
|
||||
<div>
|
||||
<div className='text-grey-600'>
|
||||
<span className='mr-1 font-bold text-black'>{getName(actor)}</span>
|
||||
<div className='text-sm'>{getUsername(actor)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FollowButton
|
||||
className='ml-auto'
|
||||
following={isFollowing}
|
||||
handle={getUsername(actor)}
|
||||
type='link'
|
||||
/>
|
||||
</ActivityItem>
|
||||
<FollowButton
|
||||
className='ml-auto'
|
||||
following={isFollowing}
|
||||
handle={getUsername(actor)}
|
||||
type='link'
|
||||
/>
|
||||
</ActivityItem>
|
||||
{index < actorData.length - 1 && <Separator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
@ -188,7 +192,7 @@ const ViewProfileModal: React.FC<ViewProfileModalProps> = ({
|
|||
onCommentClick={() => {}}
|
||||
/>
|
||||
{index < posts.length - 1 && (
|
||||
<div className="h-px w-full bg-grey-200"></div>
|
||||
<Separator />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -29,7 +29,7 @@ const MainNavigation: React.FC<MainNavigationProps> = ({
|
|||
</div>
|
||||
<div className='col-[3/4] flex items-center justify-end gap-2 px-8'>
|
||||
{page === 'home' &&
|
||||
<div className='mr-3'>
|
||||
<div>
|
||||
<Tooltip content="Inbox">
|
||||
<Button className='!px-2' icon='listview' iconColorClass={layout === 'inbox' ? 'text-black' : 'text-grey-400'} size='sm' onClick={setInbox} />
|
||||
</Tooltip>
|
||||
|
|
Loading…
Reference in a new issue