Skip to content

Commit f9eb98b

Browse files
authored
Comments: scroll to latest reply + ESCAPE to close (codesandbox#3867)
* scroll to latest reply + ESCAPE to close * remove duplicate escape * use ESC instead of ESCAPE * i'm a genius
1 parent b5eedc4 commit f9eb98b

File tree

1 file changed

+48
-12
lines changed
  • packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog

1 file changed

+48
-12
lines changed

packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Comments/Dialog/index.tsx

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { ENTER } from '@codesandbox/common/lib/utils/keycodes';
1+
import { ENTER, ESC } from '@codesandbox/common/lib/utils/keycodes';
22
import { hasPermission } from '@codesandbox/common/lib/utils/permission';
3+
34
import {
45
Avatar,
56
Element,
@@ -33,7 +34,7 @@ export const CommentDialog = props =>
3334
ReactDOM.createPortal(<Dialog {...props} />, document.body);
3435

3536
export const Dialog: React.FC = () => {
36-
const { state } = useOvermind();
37+
const { state, actions } = useOvermind();
3738
const controller = useAnimation();
3839

3940
const comment = state.comments.currentComment;
@@ -96,6 +97,17 @@ export const Dialog: React.FC = () => {
9697
setDragging(false);
9798
};
9899

100+
React.useEffect(() => {
101+
const listener = event => {
102+
if (event.which === ESC) {
103+
actions.comments.closeComment();
104+
}
105+
};
106+
107+
document.addEventListener('keydown', listener);
108+
return () => document.removeEventListener('keydown', listener);
109+
}, [actions.comments]);
110+
99111
if (!currentCommentPositions) {
100112
return null;
101113
}
@@ -152,9 +164,11 @@ export const Dialog: React.FC = () => {
152164
/>
153165

154166
<Replies
167+
key={comment.id}
155168
replies={replies}
156169
replyCount={comment.replyCount}
157170
repliesRenderedCallback={() => setRepliesRendered(true)}
171+
listRef={listRef}
158172
/>
159173
</Element>
160174
<AddReply
@@ -279,6 +293,7 @@ const DialogHeader = ({ comment, hasShadow }) => {
279293
} = useOvermind();
280294

281295
const closeDialog = () => comments.closeComment();
296+
282297
const canResolve =
283298
hasPermission(editor.currentSandbox.authorization, 'write_project') ||
284299
comment.user.id === user.id;
@@ -412,7 +427,7 @@ const CommentBody = ({ comment, editing, setEditing, hasReplies }) => {
412427
);
413428
};
414429

415-
const Replies = ({ replies, replyCount, repliesRenderedCallback }) => {
430+
const Replies = ({ replies, replyCount, listRef, repliesRenderedCallback }) => {
416431
/**
417432
* Loading animations:
418433
* 0. Wait for the dialog to have animated in view and scaled up.
@@ -424,20 +439,23 @@ const Replies = ({ replies, replyCount, repliesRenderedCallback }) => {
424439
*
425440
*/
426441

427-
const skeletonController = useAnimation();
428-
const repliesController = useAnimation();
429-
430-
/** Wait another <delay>ms after the dialog has transitioned into view */
431-
const delay = DIALOG_TRANSITION_DURATION + REPLY_TRANSITION_DELAY;
432-
const REPLY_TRANSITION_DURATION = Math.max(replyCount * 0.15, 0.5);
433-
const SKELETON_FADE_DURATION = 0.25;
434-
const SKELETON_HEIGHT = 146;
435-
const repliesLoaded = replies.length === replyCount;
436442
// initial status of replies -
437443
// this is false when it's the first time this specific comment is opened
438444
// after that it will be true because we cache replies in state
445+
const repliesLoaded = replies.length === replyCount;
439446
const repliesAlreadyLoadedOnFirstRender = React.useRef(repliesLoaded);
440447

448+
/** CONSTANTS:
449+
* Wait another <delay> after the dialog has transitioned into view
450+
* These are in s not ms
451+
*/
452+
const delay = DIALOG_TRANSITION_DURATION + REPLY_TRANSITION_DELAY;
453+
const SKELETON_FADE_DURATION = 0.25;
454+
const SKELETON_HEIGHT = 146;
455+
const REPLY_TRANSITION_DURATION = repliesLoaded
456+
? 0
457+
: Math.max(replyCount * 0.15, 0.5);
458+
441459
// current status of replies-
442460
/** Welcome to the imperative world of timeline animations
443461
*
@@ -455,6 +473,8 @@ const Replies = ({ replies, replyCount, repliesRenderedCallback }) => {
455473
*/
456474

457475
const [T, setStepInTimeline] = React.useState(-1);
476+
const skeletonController = useAnimation();
477+
const repliesController = useAnimation();
458478

459479
/*
460480
* T = 0 (DOM has rendered, animations can be started)
@@ -541,6 +561,22 @@ const Replies = ({ replies, replyCount, repliesRenderedCallback }) => {
541561
repliesController,
542562
]);
543563

564+
React.useEffect(() => {
565+
// when the animations are done, scroll to last reply
566+
let timeout;
567+
568+
if (T === 2) {
569+
timeout = window.setTimeout(() => {
570+
listRef.current.scrollTo({
571+
top: listRef.current.scrollHeight,
572+
behavior: 'smooth',
573+
});
574+
}, REPLY_TRANSITION_DURATION * 1000);
575+
}
576+
577+
return () => window.clearTimeout(timeout);
578+
}, [T, listRef, REPLY_TRANSITION_DURATION]);
579+
544580
return (
545581
<>
546582
<motion.div

0 commit comments

Comments
 (0)