mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-22 11:16:01 +03:00
Improved Inbox view UI (#21600)
ref https://linear.app/ghost/issue/AP-521/improve-inbox-view-alignmentstructure - Improved the layout, spacing and typography of Inbox view - Removed attachment counters for images in Inbox view - Added a very very small variant of `APAvatar`
This commit is contained in:
parent
ead408ed20
commit
5863c40306
@ -76,9 +76,9 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||
</div>
|
||||
) : activities.length > 0 ? (
|
||||
<>
|
||||
<div className={`mx-auto flex items-start ${layout === 'inbox' ? 'max-w-6xl gap-14' : 'gap-8'}`}>
|
||||
<div className={`mx-auto flex items-start gap-8`}>
|
||||
<div className='flex w-full min-w-0 items-start'>
|
||||
<ul className={`mx-auto flex ${layout === 'inbox' ? 'w-full max-w-full' : 'max-w-[500px]'} flex-col`}>
|
||||
<ul className={`mx-auto flex w-full flex-col ${layout === 'inbox' ? 'xxxl:max-w-[800px]' : 'max-w-[500px]'}`}>
|
||||
{activities.map((activity, index) => (
|
||||
<li
|
||||
key={activity.id}
|
||||
@ -106,7 +106,7 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div className={`sticky top-[135px] ml-auto w-full max-w-[300px] max-lg:hidden ${layout === 'inbox' ? '' : ' xxxl:fixed 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>
|
||||
{isLoadingSuggested ? (
|
||||
<LoadingIndicator size="sm" />
|
||||
|
@ -5,6 +5,7 @@ import {Button, Heading, Icon, Menu, MenuItem, showToast} from '@tryghost/admin-
|
||||
import APAvatar from '../global/APAvatar';
|
||||
|
||||
import FeedItemStats from './FeedItemStats';
|
||||
import clsx from 'clsx';
|
||||
import getRelativeTimestamp from '../../utils/get-relative-timestamp';
|
||||
import getUsername from '../../utils/get-username';
|
||||
import stripHtml from '../../utils/strip-html';
|
||||
@ -83,7 +84,7 @@ export function renderFeedAttachment(object: ObjectProperties, layout: string) {
|
||||
</div>;
|
||||
default:
|
||||
if (object.image) {
|
||||
return <img alt='attachment' className='my-3 max-h-[280px] w-full rounded-md object-cover outline outline-1 -outline-offset-1 outline-black/10' src={object.image} />;
|
||||
return <img alt='attachment' className='my-3 max-h-[280px] w-full rounded-md object-cover outline outline-1 -outline-offset-1 outline-black/[0.05]' src={object.image} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -92,20 +93,16 @@ export function renderFeedAttachment(object: ObjectProperties, layout: string) {
|
||||
function renderInboxAttachment(object: ObjectProperties) {
|
||||
const attachment = getAttachment(object);
|
||||
|
||||
const videoAttachmentStyles = 'ml-8 shrink-0 rounded-md h-[80px] w-[120px] relative';
|
||||
const imageAttachmentStyles = clsx('object-cover outline outline-1 -outline-offset-1 outline-black/[0.05]', videoAttachmentStyles);
|
||||
|
||||
if (!attachment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(attachment)) {
|
||||
const attachmentCount = attachment.length;
|
||||
|
||||
return (
|
||||
<div className='min-w-[120px]'>
|
||||
<div className='relative'>
|
||||
<img className={`h-[80px] w-[120px] rounded-md object-cover outline outline-1 -outline-offset-1 outline-black/10`} src={attachment[0].url} />
|
||||
<div className='absolute bottom-1 right-1 z-10 rounded-full border border-[rgba(255,255,255,0.25)] bg-black px-2 py-0.5 font-semibold text-white'>+ {attachmentCount - 1}</div>
|
||||
</div>
|
||||
</div>
|
||||
<img className={imageAttachmentStyles} src={attachment[0].url} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -114,14 +111,12 @@ function renderInboxAttachment(object: ObjectProperties) {
|
||||
case 'image/png':
|
||||
case 'image/gif':
|
||||
return (
|
||||
<div className='min-w-[120px]'>
|
||||
<img className={`h-[80px] w-[120px] rounded-md object-cover outline outline-1 -outline-offset-1 outline-black/10`} src={attachment.url} />
|
||||
</div>
|
||||
<img className={imageAttachmentStyles} src={attachment.url} />
|
||||
);
|
||||
case 'video/mp4':
|
||||
case 'video/webm':
|
||||
return (
|
||||
<div className='relative h-[80px]'>
|
||||
<div className={videoAttachmentStyles}>
|
||||
<video className='h-[80px] w-full rounded object-cover' src={attachment.url} />
|
||||
<div className='absolute inset-0 rounded bg-grey-900 opacity-50'></div>
|
||||
<div className='absolute inset-0 flex items-center justify-center'>
|
||||
@ -133,7 +128,7 @@ function renderInboxAttachment(object: ObjectProperties) {
|
||||
case 'audio/mpeg':
|
||||
case 'audio/ogg':
|
||||
return (
|
||||
<div className='min-w-[160px]'>
|
||||
<div className='ml-8 w-[120px]'>
|
||||
<div className='relative mb-4 mt-2 w-full'>
|
||||
<audio className='w-full' src={attachment.url} controls/>
|
||||
</div>
|
||||
@ -141,9 +136,7 @@ function renderInboxAttachment(object: ObjectProperties) {
|
||||
);
|
||||
default:
|
||||
if (object.image) {
|
||||
return <div className='min-h-[80px] min-w-[120px]'>
|
||||
<img className={`h-[80px] w-[120px] rounded-md object-cover outline outline-1 -outline-offset-1 outline-black/10`} src={object.image} />
|
||||
</div>;
|
||||
return <img className={imageAttachmentStyles} src={object.image} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -391,31 +384,25 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
|
||||
return (
|
||||
<>
|
||||
{object && (
|
||||
<div className='group/article relative -mx-4 -my-px flex min-w-0 cursor-pointer justify-between rounded-md p-4 hover:bg-grey-75' data-layout='inbox' data-object-id={object.id} onClick={onClick}>
|
||||
<div className='flex w-full min-w-0 flex-col items-start justify-between gap-1 pr-4'>
|
||||
<div className='z-10 flex w-full min-w-0 items-start gap-2 group-hover/article:border-transparent'>
|
||||
<APAvatar author={author} size='xs'/>
|
||||
<span className='min-w-0 truncate break-all font-semibold' data-test-activity-heading>{author.name}</span>
|
||||
<span className='min-w-0 truncate text-grey-700'>{getUsername(author)}</span>
|
||||
{/* <div className='flex gap-2'>
|
||||
<span className='truncate min-w-0 break-all font-semibold' data-test-activity-heading>{author.name}</span>
|
||||
<span className='min-w-0 truncate text-grey-700'>{getUsername(author)}</span>
|
||||
</div> */}
|
||||
<span className='shrink-0 whitespace-nowrap text-grey-700 before:mr-1 before:content-["·"]' title={`${timestamp}`}>{getRelativeTimestamp(date)}</span>
|
||||
<div className='group/article relative -mx-4 -my-px flex min-h-[112px] min-w-0 cursor-pointer items-center justify-between rounded-md p-4 hover:bg-grey-75' data-layout='inbox' data-object-id={object.id} onClick={onClick}>
|
||||
<div className='flex min-h-[73px] w-full min-w-0 flex-col items-start justify-start'>
|
||||
<div className='z-10 mb-1 flex w-full min-w-0 items-center gap-1.5 text-base text-grey-700 group-hover/article:border-transparent'>
|
||||
<APAvatar author={author} size='2xs'/>
|
||||
<span className='min-w-0 truncate break-all font-medium text-grey-900' data-test-activity-heading>{author.name}</span>
|
||||
<span className='min-w-0 truncate'>{getUsername(author)}</span>
|
||||
<span className='shrink-0 whitespace-nowrap before:mr-1 before:content-["·"]' title={`${timestamp}`}>{getRelativeTimestamp(date)}</span>
|
||||
</div>
|
||||
<Heading className='line-clamp-1 font-semibold leading-normal' level={5} data-test-activity-heading>
|
||||
<Heading className='mb-1 line-clamp-1 w-full max-w-[600px] text-[1.6rem] font-semibold leading-snug' level={5} data-test-activity-heading>
|
||||
{object.name ? object.name : (
|
||||
<span dangerouslySetInnerHTML={{
|
||||
__html: object.content.length > 30
|
||||
? stripHtml(object.content).substring(0, 50) + '...'
|
||||
: stripHtml(object.content)
|
||||
__html: stripHtml(object.content)
|
||||
}}></span>
|
||||
)}
|
||||
</Heading>
|
||||
<div dangerouslySetInnerHTML={({__html: stripHtml(object.content)})} className='ap-note-content w-full truncate text-[1.5rem] text-grey-700'></div>
|
||||
<div dangerouslySetInnerHTML={({__html: stripHtml(object.content)})} className='ap-note-content w-full max-w-[600px] truncate text-base leading-normal text-grey-700'></div>
|
||||
</div>
|
||||
{renderInboxAttachment(object)}
|
||||
<div className='invisible absolute right-2 top-[9px] z-[49] flex flex-col gap-2 rounded-lg bg-white p-2 shadow-md-heavy group-hover/article:visible'>
|
||||
<div className='invisible absolute right-4 top-[9px] z-[49] flex flex-col gap-2 rounded-lg bg-white p-2 shadow-md-heavy group-hover/article:visible'>
|
||||
<FeedItemStats
|
||||
commentCount={commentCount}
|
||||
layout={layout}
|
||||
|
@ -3,7 +3,7 @@ import clsx from 'clsx';
|
||||
import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {Icon} from '@tryghost/admin-x-design-system';
|
||||
|
||||
type AvatarSize = 'xs' | 'sm' | 'lg';
|
||||
type AvatarSize = '2xs' | 'xs' | 'sm' | 'lg';
|
||||
export type AvatarBadge = 'user-fill' | 'heart-fill' | 'comment-fill' | undefined;
|
||||
|
||||
interface APAvatarProps {
|
||||
@ -37,6 +37,11 @@ const APAvatar: React.FC<APAvatarProps> = ({author, size, badge}) => {
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
case '2xs':
|
||||
iconSize = 10;
|
||||
containerClass = clsx('h-4 w-4 rounded ', containerClass);
|
||||
imageClass = 'z-10 rounded w-4 h-4 object-cover';
|
||||
break;
|
||||
case 'xs':
|
||||
iconSize = 12;
|
||||
containerClass = clsx('h-5 w-5 rounded ', containerClass);
|
||||
|
Loading…
Reference in New Issue
Block a user