Skip to content

Commit 3a7fe93

Browse files
authored
Comments polish 1 (codesandbox#3728)
* rewrite message for no comments * make the inner list of comments+replies scrollable * fix margin between header and content * shadow on scroll * better starting y position
1 parent 37ceb82 commit 3a7fe93

File tree

3 files changed

+168
-139
lines changed

3 files changed

+168
-139
lines changed

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

Lines changed: 132 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Draggable from 'react-draggable';
1919

2020
import { Markdown } from './Markdown';
2121
import { Reply } from './Reply';
22+
import { useScrollTop } from './use-scroll-top';
2223

2324
export const CommentDialog = props =>
2425
ReactDOM.createPortal(<Dialog {...props} />, document.body);
@@ -32,7 +33,7 @@ export const Dialog = props => {
3233
const [editValue, setEditValue] = useState(thread.initialComment.content);
3334
const [position, setPosition] = useState({
3435
x: props.x || 200,
35-
y: props.y || 100,
36+
y: props.y || 40,
3637
});
3738

3839
const closeDialog = () => actions.editor.selectCommentThread(null);
@@ -55,9 +56,12 @@ export const Dialog = props => {
5556
});
5657
};
5758

59+
const { ref: listRef, scrollTop } = useScrollTop();
60+
5861
return (
5962
<Draggable handle=".handle" position={position} onStop={onDragStop}>
60-
<Element
63+
<Stack
64+
direction="vertical"
6165
css={css({
6266
position: 'absolute',
6367
zIndex: 2,
@@ -69,19 +73,26 @@ export const Dialog = props => {
6973
width: 420,
7074
height: 'auto',
7175
maxHeight: '80vh',
72-
overflow: 'auto',
7376
fontFamily: 'Inter, sans-serif',
77+
overflow: 'hidden',
7478
boxShadow: 2,
7579
})}
7680
>
7781
<Stack
7882
className="handle"
79-
css={{ cursor: 'move' }}
8083
align="center"
8184
justify="space-between"
8285
padding={4}
8386
paddingRight={2}
8487
marginBottom={2}
88+
css={css({
89+
cursor: 'move',
90+
zIndex: 2,
91+
boxShadow: theme =>
92+
scrollTop > 0
93+
? `0px 32px 32px ${theme.colors.dialog.background}`
94+
: 'none',
95+
})}
8596
>
8697
<Text size={3} weight="bold">
8798
Comment
@@ -113,141 +124,136 @@ export const Dialog = props => {
113124
</Stack>
114125

115126
{thread && (
116-
<>
117-
<Stack
118-
align="flex-start"
119-
justify="space-between"
120-
marginBottom={4}
121-
marginLeft={4}
122-
marginRight={2}
123-
>
124-
<Stack gap={2} align="center">
125-
<Avatar user={thread.initialComment.user} />
126-
<Stack direction="vertical" justify="center" gap={1}>
127-
<Link
128-
size={3}
129-
weight="bold"
130-
href={`/u/${thread.initialComment.user.username}`}
131-
variant="body"
132-
>
133-
{thread.initialComment.user.username}
134-
</Link>
135-
<Text size={2} variant="muted">
136-
{formatDistance(new Date(thread.insertedAt), new Date(), {
137-
addSuffix: true,
138-
})}
139-
</Text>
127+
<Stack direction="vertical" css={{ overflow: 'auto' }} ref={listRef}>
128+
<Stack direction="vertical" gap={4}>
129+
<Stack
130+
align="flex-start"
131+
justify="space-between"
132+
marginLeft={4}
133+
marginRight={2}
134+
>
135+
<Stack gap={2} align="center">
136+
<Avatar user={thread.initialComment.user} />
137+
<Stack direction="vertical" justify="center" gap={1}>
138+
<Link
139+
size={3}
140+
weight="bold"
141+
href={`/u/${thread.initialComment.user.username}`}
142+
variant="body"
143+
>
144+
{thread.initialComment.user.username}
145+
</Link>
146+
<Text size={2} variant="muted">
147+
{formatDistance(new Date(thread.insertedAt), new Date(), {
148+
addSuffix: true,
149+
})}
150+
</Text>
151+
</Stack>
140152
</Stack>
153+
{state.user.id === thread.initialComment.user.id && (
154+
<Stack align="center">
155+
<Menu>
156+
<Menu.IconButton
157+
name="more"
158+
title="Comment actions"
159+
size={12}
160+
/>
161+
<Menu.List>
162+
<Menu.Item
163+
onSelect={() =>
164+
actions.editor.deleteComment({
165+
threadId: thread.id,
166+
commentId: thread.initialComment.id,
167+
})
168+
}
169+
>
170+
Delete
171+
</Menu.Item>
172+
<Menu.Item onSelect={() => setEdit(true)}>
173+
Edit Comment
174+
</Menu.Item>
175+
</Menu.List>
176+
</Menu>
177+
</Stack>
178+
)}
141179
</Stack>
142-
{state.user.id === thread.initialComment.user.id && (
143-
<Stack align="center">
144-
<Menu>
145-
<Menu.IconButton
146-
name="more"
147-
title="Comment actions"
148-
size={12}
149-
/>
150-
<Menu.List>
151-
<Menu.Item
152-
onSelect={() =>
153-
actions.editor.deleteComment({
180+
<Element
181+
as={edit ? 'div' : 'p'}
182+
marginY={0}
183+
marginX={4}
184+
paddingBottom={6}
185+
css={css({
186+
borderBottom: '1px solid',
187+
borderColor: 'sideBar.border',
188+
})}
189+
>
190+
{!edit ? (
191+
<Markdown source={thread.initialComment.content} />
192+
) : (
193+
<>
194+
<Element marginBottom={2}>
195+
<Textarea
196+
autosize
197+
value={editValue}
198+
onChange={e => setEditValue(e.target.value)}
199+
/>
200+
</Element>
201+
<Element
202+
css={css({
203+
display: 'grid',
204+
gridTemplateColumns: '1fr 1fr',
205+
gridGap: 2,
206+
})}
207+
>
208+
<Button variant="link" onClick={() => setEdit(false)}>
209+
Cancel
210+
</Button>
211+
212+
<Button
213+
disabled={!editValue}
214+
variant="secondary"
215+
onClick={async () => {
216+
await actions.editor.updateComment({
154217
threadId: thread.id,
155218
commentId: thread.initialComment.id,
156-
})
157-
}
219+
content: editValue,
220+
});
221+
setEdit(false);
222+
}}
158223
>
159-
Delete
160-
</Menu.Item>
161-
<Menu.Item onSelect={() => setEdit(true)}>
162-
Edit Comment
163-
</Menu.Item>
164-
</Menu.List>
165-
</Menu>
166-
</Stack>
167-
)}
224+
Save
225+
</Button>
226+
</Element>
227+
</>
228+
)}
229+
</Element>
168230
</Stack>
169-
<Element
170-
as={edit ? 'div' : 'p'}
171-
marginY={0}
172-
marginX={4}
173-
paddingBottom={6}
174-
css={css({
175-
borderBottom: '1px solid',
176-
borderColor: 'sideBar.border',
231+
<>
232+
{thread.comments.map((reply, i) => {
233+
if (i === 0) return null;
234+
return <Reply reply={reply} threadId={thread.id} />;
177235
})}
178-
>
179-
{!edit ? (
180-
<Markdown source={thread.initialComment.content} />
181-
) : (
182-
<>
183-
<Element marginBottom={2}>
184-
<Textarea
185-
autosize
186-
value={editValue}
187-
onChange={e => setEditValue(e.target.value)}
188-
/>
189-
</Element>
190-
<Element
191-
css={css({
192-
display: 'grid',
193-
gridTemplateColumns: '1fr 1fr',
194-
gridGap: 2,
195-
})}
196-
>
197-
<Button variant="link" onClick={() => setEdit(false)}>
198-
Cancel
199-
</Button>
200-
201-
<Button
202-
disabled={!editValue}
203-
variant="secondary"
204-
onClick={async () => {
205-
await actions.editor.updateComment({
206-
threadId: thread.id,
207-
commentId: thread.initialComment.id,
208-
content: editValue,
209-
});
210-
setEdit(false);
211-
}}
212-
>
213-
Save
214-
</Button>
215-
</Element>
216-
</>
217-
)}
218-
</Element>
219-
</>
236+
</>
237+
</Stack>
220238
)}
221239

222-
{thread &&
223-
thread.comments.map((reply, i) => {
224-
if (i === 0) return null;
225-
return <Reply reply={reply} threadId={thread.id} />;
226-
})}
227-
228-
<Element
240+
<Textarea
241+
autosize
229242
css={css({
243+
overflow: 'hidden',
244+
border: 'none',
245+
display: 'block',
230246
borderTop: '1px solid',
231247
borderColor: 'sideBar.border',
232248
})}
233-
>
234-
<Textarea
235-
autosize
236-
css={css({
237-
overflow: 'hidden',
238-
239-
border: 'none',
240-
display: 'block',
241-
})}
242-
value={value}
243-
onChange={e => setValue(e.target.value)}
244-
placeholder={thread ? 'Reply' : 'Write a comment...'}
245-
onKeyDown={event => {
246-
if (event.keyCode === ENTER && !event.shiftKey) onSubmit();
247-
}}
248-
/>
249-
</Element>
250-
</Element>
249+
value={value}
250+
onChange={e => setValue(e.target.value)}
251+
placeholder={thread ? 'Reply' : 'Write a comment...'}
252+
onKeyDown={event => {
253+
if (event.keyCode === ENTER && !event.shiftKey) onSubmit();
254+
}}
255+
/>
256+
</Stack>
251257
</Draggable>
252258
);
253259
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
3+
const useScrollTop = () => {
4+
const ref = React.useRef(null);
5+
const [scrollTop, setScrollTop] = React.useState(0);
6+
7+
React.useEffect(() => {
8+
const element = ref.current;
9+
10+
const handleScroll = (event: React.UIEvent<HTMLElement>) => {
11+
setScrollTop(event.currentTarget.scrollTop);
12+
};
13+
14+
if (element) {
15+
element.addEventListener('scroll', handleScroll);
16+
}
17+
18+
return () => {
19+
if (element) {
20+
element.removeEventListener('scroll', handleScroll);
21+
}
22+
};
23+
}, []);
24+
25+
return { scrollTop, ref };
26+
};
27+
28+
export { useScrollTop };

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ export const CommentThreads: React.FC = () => {
3939
}
4040
};
4141

42-
const all =
43-
selectedCommentsFilter === CommentsFilterOption.OPEN ||
44-
selectedCommentsFilter === CommentsFilterOption.ALL;
45-
4642
const Empty = () => (
4743
<Stack
4844
direction="vertical"
@@ -57,15 +53,14 @@ export const CommentThreads: React.FC = () => {
5753
color="mutedForeground"
5854
css={{ opacity: 0.2 }}
5955
/>
60-
<Text block align="center" variant="muted">
61-
There are no {getSelectedFilter()} comments.{' '}
62-
{all && (
63-
<>
64-
{/* Leave a comment by clicking anywhere within a file or */}
65-
Write a global comment below.
66-
</>
67-
)}
68-
</Text>
56+
<div>
57+
<Text block align="center" variant="muted">
58+
There are no {getSelectedFilter()} comments.
59+
</Text>
60+
<Text block align="center" variant="muted">
61+
Comment on code by clicking within a file, or add a comment below.
62+
</Text>
63+
</div>
6964
</Stack>
7065
);
7166

0 commit comments

Comments
 (0)