nicolium: announcement reactions bar UI consistency
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -85,7 +85,7 @@ const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate, short, ma
|
||||
});
|
||||
|
||||
if (reduceMotion) {
|
||||
return <>{formattedValue}</>;
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { animated, type AnimatedProps } from '@react-spring/web';
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import AnimatedNumber from '@/components/animated-number';
|
||||
import unicodeMapping from '@/features/emoji/mapping';
|
||||
@ -10,6 +11,13 @@ import Emoji from './emoji';
|
||||
|
||||
import type { AnnouncementReaction, CustomEmoji } from 'pl-api';
|
||||
|
||||
const messages = defineMessages({
|
||||
emojiCount: {
|
||||
id: 'status.reactions.label',
|
||||
defaultMessage: '{count} {count, plural, one {person} other {people}} reacted with {emoji}',
|
||||
},
|
||||
});
|
||||
|
||||
interface IReaction {
|
||||
announcementId: string;
|
||||
reaction: AnnouncementReaction;
|
||||
@ -18,6 +26,7 @@ interface IReaction {
|
||||
}
|
||||
|
||||
const Reaction: React.FC<IReaction> = ({ announcementId, reaction, emojiMap, style }) => {
|
||||
const intl = useIntl();
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const { addReaction, removeReaction } = useAnnouncements();
|
||||
@ -46,25 +55,23 @@ const Reaction: React.FC<IReaction> = ({ announcementId, reaction, emojiMap, sty
|
||||
|
||||
return (
|
||||
<animated.button
|
||||
className={clsx(
|
||||
'flex shrink-0 items-center gap-1.5 rounded-sm bg-gray-100 px-1.5 py-1 transition-colors dark:bg-primary-900',
|
||||
{
|
||||
'bg-gray-200 dark:bg-primary-800': hovered,
|
||||
'bg-primary-200 dark:bg-primary-500': reaction.me,
|
||||
},
|
||||
)}
|
||||
className={clsx('⁂-status-reactions-bar__button', {
|
||||
'⁂-status-reactions-bar__button--active': reaction.me,
|
||||
})}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
title={`:${shortCode}:`}
|
||||
title={intl.formatMessage(messages.emojiCount, {
|
||||
emoji: `:${shortCode}:`,
|
||||
count: reaction.count,
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
<span className='block size-4'>
|
||||
<Emoji hovered={hovered} emoji={reaction.name} emojiMap={emojiMap} />
|
||||
</span>
|
||||
<span className='block min-w-[9px] text-center text-xs font-medium text-primary-600 dark:text-white'>
|
||||
<Emoji hovered={hovered} emoji={reaction.name} emojiMap={emojiMap} />
|
||||
|
||||
<p>
|
||||
<AnimatedNumber value={reaction.count} />
|
||||
</span>
|
||||
</p>
|
||||
</animated.button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
import { useTransition } from '@react-spring/web';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import EmojiPickerDropdown from '@/features/emoji/containers/emoji-picker-dropdown-container';
|
||||
import { useAnnouncements } from '@/queries/announcements/use-announcements';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
import Icon from '../ui/icon';
|
||||
|
||||
import Reaction from './reaction';
|
||||
|
||||
import type { Emoji, NativeEmoji } from '@/features/emoji';
|
||||
import type { AnnouncementReaction, CustomEmoji } from 'pl-api';
|
||||
|
||||
const messages = defineMessages({
|
||||
addEmoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||
});
|
||||
|
||||
interface IReactionsBar {
|
||||
announcementId: string;
|
||||
reactions: Array<AnnouncementReaction>;
|
||||
@ -17,6 +24,7 @@ interface IReactionsBar {
|
||||
}
|
||||
|
||||
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emojiMap }) => {
|
||||
const intl = useIntl();
|
||||
const { reduceMotion } = useSettings();
|
||||
const { addReaction } = useAnnouncements();
|
||||
|
||||
@ -41,7 +49,7 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emoj
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap items-center gap-1'>
|
||||
<div className='⁂-status-reactions-bar'>
|
||||
{transitions(({ scale }, reaction) => (
|
||||
<Reaction
|
||||
key={reaction.name}
|
||||
@ -52,7 +60,16 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emoj
|
||||
/>
|
||||
))}
|
||||
|
||||
{visibleReactions.length < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} />}
|
||||
{visibleReactions.length < 8 && (
|
||||
<EmojiPickerDropdown onPickEmoji={handleEmojiPick}>
|
||||
<button
|
||||
className='⁂-status-reactions-bar__picker-button emoji-picker-dropdown'
|
||||
title={intl.formatMessage(messages.addEmoji)}
|
||||
>
|
||||
<Icon src={require('@phosphor-icons/core/regular/smiley-sticker.svg')} aria-hidden />
|
||||
</button>
|
||||
</EmojiPickerDropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -90,7 +90,6 @@ const StatusReaction: React.FC<IStatusReaction> = ({
|
||||
className={clsx('⁂-status-reactions-bar__button', {
|
||||
'⁂-status-reactions-bar__button--active': reaction.me,
|
||||
})}
|
||||
key={reaction.name}
|
||||
onClick={handleClick}
|
||||
title={intl.formatMessage(messages.emojiCount, {
|
||||
emoji: `:${shortCode}:`,
|
||||
|
||||
@ -98,112 +98,110 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
||||
if (!account) return null;
|
||||
|
||||
return (
|
||||
<div className='border-box'>
|
||||
<div ref={node} className='detailed-actualStatus' tabIndex={-1}>
|
||||
{renderStatusInfo()}
|
||||
<div ref={node} className='⁂-detailed-status' tabIndex={-1}>
|
||||
{renderStatusInfo()}
|
||||
|
||||
{actualStatus.rss_feed ? (
|
||||
<RssFeedInfo feed={actualStatus.rss_feed} timestamp={actualStatus.created_at} />
|
||||
) : (
|
||||
<div className='mb-4'>
|
||||
<Account
|
||||
key={account.id}
|
||||
account={account}
|
||||
avatarSize={42}
|
||||
hideActions
|
||||
approvalStatus={actualStatus.approval_status}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{actualStatus.rss_feed ? (
|
||||
<RssFeedInfo feed={actualStatus.rss_feed} timestamp={actualStatus.created_at} />
|
||||
) : (
|
||||
<div className='mb-4'>
|
||||
<Account
|
||||
key={account.id}
|
||||
account={account}
|
||||
avatarSize={42}
|
||||
hideActions
|
||||
approvalStatus={actualStatus.approval_status}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<StatusReplyMentions status={actualStatus} />
|
||||
<StatusReplyMentions status={actualStatus} />
|
||||
|
||||
<Stack className='relative z-0'>
|
||||
<Stack space={4}>
|
||||
<StatusContent status={actualStatus} textSize='lg' translatable withMedia={withMedia} />
|
||||
</Stack>
|
||||
<Stack className='relative z-0'>
|
||||
<Stack space={4}>
|
||||
<StatusContent status={actualStatus} textSize='lg' translatable withMedia={withMedia} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{!status.rss_feed && (
|
||||
<>
|
||||
<StatusReactionsBar status={actualStatus} />
|
||||
{!status.rss_feed && (
|
||||
<>
|
||||
<StatusReactionsBar status={actualStatus} />
|
||||
|
||||
<HStack space={2} justifyContent='between' alignItems='center' className='py-3' wrap>
|
||||
<StatusInteractionBar status={actualStatus} />
|
||||
<HStack space={2} justifyContent='between' alignItems='center' className='py-3' wrap>
|
||||
<StatusInteractionBar status={actualStatus} />
|
||||
|
||||
<HStack space={1} alignItems='center'>
|
||||
<span>
|
||||
<Text tag='span' theme='muted' size='sm'>
|
||||
<HStack space={1} alignItems='center' wrap>
|
||||
<a
|
||||
href={actualStatus.url}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='hover:underline'
|
||||
>
|
||||
<FormattedDate
|
||||
value={new Date(actualStatus.created_at)}
|
||||
hour12
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='numeric'
|
||||
minute='2-digit'
|
||||
/>
|
||||
</a>
|
||||
<HStack space={1} alignItems='center'>
|
||||
<span>
|
||||
<Text tag='span' theme='muted' size='sm'>
|
||||
<HStack space={1} alignItems='center' wrap>
|
||||
<a
|
||||
href={actualStatus.url}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='hover:underline'
|
||||
>
|
||||
<FormattedDate
|
||||
value={new Date(actualStatus.created_at)}
|
||||
hour12
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='numeric'
|
||||
minute='2-digit'
|
||||
/>
|
||||
</a>
|
||||
|
||||
{actualStatus.application && (
|
||||
<>
|
||||
<span className='⁂-separator' />
|
||||
<a
|
||||
href={actualStatus.application.website ?? '#'}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='hover:underline'
|
||||
title={intl.formatMessage(messages.applicationName, {
|
||||
name: actualStatus.application.name,
|
||||
})}
|
||||
>
|
||||
{actualStatus.application.name}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{actualStatus.application && (
|
||||
<>
|
||||
<span className='⁂-separator' />
|
||||
<a
|
||||
href={actualStatus.application.website ?? '#'}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='hover:underline'
|
||||
title={intl.formatMessage(messages.applicationName, {
|
||||
name: actualStatus.application.name,
|
||||
})}
|
||||
>
|
||||
{actualStatus.application.name}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
|
||||
{actualStatus.edited_at && (
|
||||
<>
|
||||
<span className='⁂-separator' />
|
||||
<button
|
||||
className='inline hover:underline'
|
||||
onClick={handleOpenCompareHistoryModal}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='status.edited'
|
||||
defaultMessage='Edited {date}'
|
||||
values={{
|
||||
date: intl.formatDate(new Date(actualStatus.edited_at), {
|
||||
hour12: true,
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</Text>
|
||||
</span>
|
||||
{actualStatus.edited_at && (
|
||||
<>
|
||||
<span className='⁂-separator' />
|
||||
<button
|
||||
className='inline hover:underline'
|
||||
onClick={handleOpenCompareHistoryModal}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='status.edited'
|
||||
defaultMessage='Edited {date}'
|
||||
values={{
|
||||
date: intl.formatDate(new Date(actualStatus.edited_at), {
|
||||
hour12: true,
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</Text>
|
||||
</span>
|
||||
|
||||
<StatusTypeIcon visibility={actualStatus.visibility} />
|
||||
<StatusTypeIcon visibility={actualStatus.visibility} />
|
||||
|
||||
<StatusLanguagePicker status={actualStatus} showLabel />
|
||||
</HStack>
|
||||
<StatusLanguagePicker status={actualStatus} showLabel />
|
||||
</HStack>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</HStack>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -303,7 +303,7 @@ const Thread = ({
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
(node.current?.querySelector('.detailed-actualStatus') as HTMLDivElement)?.focus();
|
||||
(node.current?.querySelector('.⁂-detailed-status') as HTMLDivElement)?.focus();
|
||||
}, 100);
|
||||
}, 0);
|
||||
}, [status.id, statusIndex]);
|
||||
|
||||
@ -20,8 +20,15 @@
|
||||
@apply mb-1 block text-sm text-gray-700 dark:text-gray-600;
|
||||
}
|
||||
|
||||
.⁂-status,
|
||||
.⁂-detailed-status {
|
||||
.⁂-status-reactions-bar {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.⁂-status-reactions-bar {
|
||||
@apply flex gap-2 flex-wrap pt-2;
|
||||
@apply flex gap-2 flex-wrap;
|
||||
|
||||
&__button {
|
||||
@apply flex cursor-pointer items-center gap-2 overflow-hidden rounded-md border border-gray-400 px-1.5 py-1 transition-all bg-transparent dark:border-primary-700 dark:bg-primary-700 black:border-primary-800 black:bg-primary-800;
|
||||
|
||||
Reference in New Issue
Block a user