Add inline resolve action on comment threads (#7977)

* Add inline resolve action on comment threads

* perf refactor
This commit is contained in:
Tom Moor
2024-11-18 21:36:44 -05:00
committed by GitHub
parent 326f733d4c
commit c629006642
3 changed files with 72 additions and 35 deletions

View File

@@ -2,7 +2,6 @@ import { ReactionIcon } from "outline-icons";
import React from "react";
import { useTranslation } from "react-i18next";
import { PopoverDisclosure, usePopoverState } from "reakit";
import styled from "styled-components";
import EventBoundary from "@shared/components/EventBoundary";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
@@ -11,6 +10,7 @@ import Popover from "~/components/Popover";
import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import useWindowSize from "~/hooks/useWindowSize";
import Tooltip from "../Tooltip";
const EmojiPanel = React.lazy(
() => import("~/components/IconPicker/components/EmojiPanel")
@@ -98,15 +98,22 @@ const ReactionPicker: React.FC<Props> = ({
<>
<PopoverDisclosure {...popover}>
{(props) => (
<PopoverButton
{...props}
aria-label={t("Reaction picker")}
className={className}
onClick={handlePopoverButtonClick}
size={size}
<Tooltip
content={t("Add reaction")}
placement="top"
delay={500}
hideOnClick
>
<ReactionIcon size={22} />
</PopoverButton>
<NudeButton
{...props}
aria-label={t("Reaction picker")}
className={className}
onClick={handlePopoverButtonClick}
size={size}
>
<ReactionIcon size={22} />
</NudeButton>
</Tooltip>
)}
</PopoverDisclosure>
<Popover
@@ -151,8 +158,4 @@ const Placeholder = React.memo(
);
Placeholder.displayName = "ReactionPickerPlaceholder";
const PopoverButton = styled(NudeButton)`
border-radius: 50%;
`;
export default ReactionPicker;

View File

@@ -1,6 +1,7 @@
import { differenceInMilliseconds } from "date-fns";
import { action } from "mobx";
import { observer } from "mobx-react";
import { DoneIcon } from "outline-icons";
import { darken } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -16,10 +17,14 @@ import Comment from "~/models/Comment";
import { Avatar } from "~/components/Avatar";
import ButtonSmall from "~/components/ButtonSmall";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import ReactionList from "~/components/Reactions/ReactionList";
import ReactionPicker from "~/components/Reactions/ReactionPicker";
import Text from "~/components/Text";
import Time from "~/components/Time";
import Tooltip from "~/components/Tooltip";
import { resolveCommentFactory } from "~/actions/definitions/comments";
import useActionContext from "~/hooks/useActionContext";
import useBoolean from "~/hooks/useBoolean";
import useCurrentUser from "~/hooks/useCurrentUser";
import CommentMenu from "~/menus/CommentMenu";
@@ -242,11 +247,13 @@ function CommentThreadItem({
onRemoveReaction={handleRemoveReaction}
picker={
!comment.isResolved ? (
<StyledReactionPicker
<Action
as={ReactionPicker}
onSelect={handleAddReaction}
onOpen={disableScroll}
onClose={enableScroll}
size={28}
rounded
/>
) : undefined
}
@@ -257,14 +264,20 @@ function CommentThreadItem({
<EventBoundary>
{!isEditing && (
<Actions gap={4} dir={dir}>
{firstOfThread && (
<ResolveButton onUpdate={handleUpdate} comment={comment} />
)}
{!comment.isResolved && (
<StyledReactionPicker
<Action
as={ReactionPicker}
onSelect={handleAddReaction}
onOpen={disableScroll}
onClose={enableScroll}
rounded
/>
)}
<StyledMenu
<Action
as={CommentMenu}
comment={comment}
onEdit={setEditing}
onDelete={handleDelete}
@@ -278,6 +291,38 @@ function CommentThreadItem({
);
}
const ResolveButton = ({
comment,
onUpdate,
}: {
comment: Comment;
onUpdate: (attrs: { resolved: boolean }) => void;
}) => {
const context = useActionContext();
const { t } = useTranslation();
return (
<Tooltip
content={t("Mark as resolved")}
placement="top"
delay={500}
hideOnClick
>
<Action
as={NudeButton}
context={context}
action={resolveCommentFactory({
comment,
onResolve: () => onUpdate({ resolved: true }),
})}
rounded
>
<DoneIcon size={22} outline />
</Action>
</Tooltip>
);
};
const StyledCommentEditor = styled(CommentEditor)`
${(props) =>
!props.readOnly &&
@@ -308,25 +353,13 @@ const Body = styled.form`
border-radius: 2px;
`;
const StyledMenu = styled(CommentMenu)`
color: ${s("textSecondary")};
svg {
fill: currentColor;
opacity: 0.5;
}
&: ${hover}, &[aria-expanded= "true"] {
background: ${s("backgroundQuaternary")};
svg {
opacity: 0.75;
}
}
`;
const StyledReactionPicker = styled(ReactionPicker)`
const Action = styled.span<{ rounded?: boolean }>`
color: ${s("textSecondary")};
${(props) =>
props.rounded &&
css`
border-radius: 50%;
`}
svg {
fill: currentColor;
@@ -352,7 +385,7 @@ const Actions = styled(Flex)<{ dir?: "rtl" | "ltr" }>`
background: ${s("backgroundSecondary")};
padding-left: 4px;
&:has(${StyledReactionPicker}[aria-expanded="true"], ${StyledMenu}[aria-expanded="true"]) {
&:has(${Action}[aria-expanded="true"]) {
opacity: 1;
}
`;

View File

@@ -308,6 +308,7 @@
"{{ firstUsername }} and {{ secondUsername }} reacted with {{ emoji }}": "{{ firstUsername }} and {{ secondUsername }} reacted with {{ emoji }}",
"{{ firstUsername }} and {{ count }} others reacted with {{ emoji }}": "{{ firstUsername }} and {{ count }} other reacted with {{ emoji }}",
"{{ firstUsername }} and {{ count }} others reacted with {{ emoji }}_plural": "{{ firstUsername }} and {{ count }} others reacted with {{ emoji }}",
"Add reaction": "Add reaction",
"Reaction picker": "Reaction picker",
"Could not load reactions": "Could not load reactions",
"Reaction": "Reaction",