Merge branch 'reply-popover' into 'develop'

Add popover when trying to reply to Group status

See merge request soapbox-pub/soapbox!2386
develop^2
Chewbacca 2023-03-30 13:23:47 +00:00
commit 01458c3003
5 zmienionych plików z 150 dodań i 14 usunięć

Wyświetl plik

@ -0,0 +1,99 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { Button, Divider, HStack, Popover, Stack, Text } from 'soapbox/components/ui';
import GroupMemberCount from 'soapbox/features/group/components/group-member-count';
import GroupPrivacy from 'soapbox/features/group/components/group-privacy';
import GroupAvatar from '../group-avatar';
import type { Group } from 'soapbox/schemas';
interface IGroupPopoverContainer {
children: React.ReactElement<any, string | React.JSXElementConstructor<any>>
isEnabled: boolean
group: Group
}
const messages = defineMessages({
title: { id: 'group.popover.title', defaultMessage: 'Membership required' },
summary: { id: 'group.popover.summary', defaultMessage: 'You must be a member of the group in order to reply to this status.' },
action: { id: 'group.popover.action', defaultMessage: 'View Group' },
});
const GroupPopover = (props: IGroupPopoverContainer) => {
const { children, group, isEnabled } = props;
const intl = useIntl();
if (!isEnabled) {
return children;
}
return (
<Popover
interaction='click'
referenceElementClassName='cursor-pointer'
content={
<Stack space={4} className='w-80'>
<Stack
className='relative h-60 rounded-lg bg-white dark:border-primary-800 dark:bg-primary-900'
data-testid='group-card'
>
{/* Group Cover Image */}
<Stack grow className='relative basis-1/2 rounded-t-lg bg-primary-100 dark:bg-gray-800'>
{group.header && (
<img
className='absolute inset-0 h-full w-full rounded-t-lg object-cover'
src={group.header}
alt=''
/>
)}
</Stack>
{/* Group Avatar */}
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<GroupAvatar group={group} size={64} withRing />
</div>
{/* Group Info */}
<Stack alignItems='center' justifyContent='end' grow className='basis-1/2 py-4' space={0.5}>
<Text size='lg' weight='bold' dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
<GroupPrivacy group={group} />
<GroupMemberCount group={group} />
</HStack>
</Stack>
</Stack>
<Divider />
<Stack space={0.5} className='px-4'>
<Text weight='semibold'>
{intl.formatMessage(messages.title)}
</Text>
<Text theme='muted'>
{intl.formatMessage(messages.summary)}
</Text>
</Stack>
<div className='px-4 pb-4'>
<Link to={`/groups/${group.id}`}>
<Button type='button' theme='secondary' block>
{intl.formatMessage(messages.action)}
</Button>
</Link>
</div>
</Stack>
}
isFlush
children={
<div className='inline-block'>{children}</div>
}
/>
);
};
export default GroupPopover;

Wyświetl plik

@ -24,6 +24,8 @@ import { isLocal, isRemote } from 'soapbox/utils/accounts';
import copy from 'soapbox/utils/copy';
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts';
import GroupPopover from './groups/popover/group-popover';
import type { Menu } from 'soapbox/components/dropdown-menu';
import type { Account, Group, Status } from 'soapbox/types/entities';
@ -617,14 +619,19 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
grow={space === 'expand'}
onClick={e => e.stopPropagation()}
>
<StatusActionButton
title={replyTitle}
icon={require('@tabler/icons/message-circle-2.svg')}
onClick={handleReplyClick}
count={replyCount}
text={withLabels ? intl.formatMessage(messages.reply) : undefined}
disabled={replyDisabled}
/>
<GroupPopover
group={status.group as any}
isEnabled={replyDisabled}
>
<StatusActionButton
title={replyTitle}
icon={require('@tabler/icons/message-circle-2.svg')}
onClick={handleReplyClick}
count={replyCount}
text={withLabels ? intl.formatMessage(messages.reply) : undefined}
disabled={replyDisabled}
/>
</GroupPopover>
{(features.quotePosts && me) ? (
<DropdownMenu

Wyświetl plik

@ -1,18 +1,28 @@
import {
arrow,
autoPlacement,
FloatingArrow,
offset,
useClick,
useDismiss,
useFloating,
useHover,
useInteractions,
useTransitionStyles,
} from '@floating-ui/react';
import clsx from 'clsx';
import React, { useRef, useState } from 'react';
interface IPopover {
children: React.ReactElement<any, string | React.JSXElementConstructor<any>>
/** The content of the popover */
content: React.ReactNode
/** Should we remove padding on the Popover */
isFlush?: boolean
/** Should the popover trigger via click or hover */
interaction?: 'click' | 'hover'
/** Add a class to the reference (trigger) element */
referenceElementClassName?: string
}
/**
@ -22,7 +32,7 @@ interface IPopover {
* of information.
*/
const Popover: React.FC<IPopover> = (props) => {
const { children, content } = props;
const { children, content, referenceElementClassName, interaction = 'hover', isFlush = false } = props;
const [isOpen, setIsOpen] = useState<boolean>(false);
@ -33,6 +43,9 @@ const Popover: React.FC<IPopover> = (props) => {
onOpenChange: setIsOpen,
placement: 'top',
middleware: [
autoPlacement({
allowedPlacements: ['top', 'bottom'],
}),
offset(10),
arrow({
element: arrowRef,
@ -40,8 +53,6 @@ const Popover: React.FC<IPopover> = (props) => {
],
});
const click = useClick(context);
const dismiss = useDismiss(context);
const { isMounted, styles } = useTransitionStyles(context, {
initial: {
opacity: 0,
@ -53,8 +64,13 @@ const Popover: React.FC<IPopover> = (props) => {
},
});
const click = useClick(context, { enabled: interaction === 'click' });
const hover = useHover(context, { enabled: interaction === 'hover' });
const dismiss = useDismiss(context);
const { getReferenceProps, getFloatingProps } = useInteractions([
click,
hover,
dismiss,
]);
@ -63,7 +79,7 @@ const Popover: React.FC<IPopover> = (props) => {
{React.cloneElement(children, {
ref: refs.setReference,
...getReferenceProps(),
className: 'cursor-help',
className: clsx(children.props.className, referenceElementClassName),
})}
{(isMounted) && (
@ -75,12 +91,22 @@ const Popover: React.FC<IPopover> = (props) => {
left: x ?? 0,
...styles,
}}
className='rounded-lg bg-white p-6 shadow-2xl dark:bg-gray-900 dark:ring-2 dark:ring-primary-700'
className={
clsx({
'z-40 rounded-lg bg-white shadow-2xl dark:bg-gray-900 dark:ring-2 dark:ring-primary-700': true,
'p-6': !isFlush,
})
}
{...getFloatingProps()}
>
{content}
<FloatingArrow ref={arrowRef} context={context} className='fill-white dark:hidden' />
<FloatingArrow
ref={arrowRef}
context={context}
className='-ml-2 fill-white dark:hidden' /** -ml-2 to fix offcenter arrow */
tipRadius={3}
/>
</div>
)}
</>

Wyświetl plik

@ -10,6 +10,7 @@ interface IGroupPolicy {
const GroupPrivacy = ({ group }: IGroupPolicy) => (
<Popover
referenceElementClassName='cursor-help'
content={
<Stack space={4} alignItems='center' className='w-72'>
<div className='rounded-full bg-gray-200 p-3 dark:bg-gray-800'>

Wyświetl plik

@ -787,6 +787,9 @@
"group.leave": "Leave Group",
"group.leave.success": "Left the group",
"group.manage": "Manage Group",
"group.popover.action": "View Group",
"group.popover.summary": "You must be a member of the group in order to reply to this status.",
"group.popover.title": "Membership required",
"group.privacy.locked": "Private",
"group.privacy.locked.full": "Private Group",
"group.privacy.locked.info": "Discoverable. Users can join after their request is approved.",