sforkowany z mirror/soapbox
Merge branch 'reply-popover' into 'develop'
Add popover when trying to reply to Group status See merge request soapbox-pub/soapbox!2386develop^2
commit
01458c3003
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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.",
|
||||
|
|
Ładowanie…
Reference in New Issue