Skip to content

Commit f446256

Browse files
authored
Feat/comments pane (#171)
* feat/comments-pane * fix: overflow and drawer swipe * feat: Comment fragment * fix: comments added * fix: comment fragment * feat: Comments reply * fix: message input position * fix: post type shifted to types file * fix: one level deep only * fix: drawer should only be render on mobile * fix: comments on layout page * fix: format
1 parent d470d6a commit f446256

File tree

16 files changed

+448
-60
lines changed

16 files changed

+448
-60
lines changed
Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { CommentType } from './types';
2+
13
export const dummyPosts = Array.from({ length: 100 }, (_, i) => ({
24
id: i + 1,
35
avatar: 'https://www.gravatar.com/avatar/2c7d99fe281ecd3bcd65ab915bac6dd5?s=250',
@@ -9,10 +11,74 @@ export const dummyPosts = Array.from({ length: 100 }, (_, i) => ({
911
count: {
1012
likes: Math.floor(Math.random() * 500),
1113
comments: Math.floor(Math.random() * 200)
12-
},
13-
callback: {
14-
like: () => alert(`Like clicked on post ${i + 1}`),
15-
comment: () => alert(`Comment clicked on post ${i + 1}`),
16-
menu: () => alert(`Menu clicked on post ${i + 1}`)
1714
}
1815
}));
16+
17+
export const comments: CommentType[] = Array.from({ length: 50 }, (_, i) => ({
18+
userImgSrc: 'https://picsum.photos/800',
19+
name: `user${i + 1}`,
20+
commentId: `${i + 1}p`,
21+
comment: `this is the dummy comment which is commented by user${i + 1}`,
22+
isUpVoted: false,
23+
isDownVoted: false,
24+
upVotes: 0,
25+
time: '2 minutes ago',
26+
replies: [
27+
{
28+
userImgSrc: 'https://picsum.photos/800',
29+
name: `user${i + 1}x`,
30+
commentId: `${i + 1}x`,
31+
comment: `this is the dummy reply which is replied by another${i}x`,
32+
isUpVoted: false,
33+
isDownVoted: false,
34+
upVotes: 0,
35+
time: '1 minute ago',
36+
replies: [
37+
{
38+
userImgSrc: 'https://picsum.photos/800',
39+
name: `user${i + 1}a`,
40+
commentId: `${i + 1}a`,
41+
comment: `this is the dummy reply which is replied by another${i}a`,
42+
isUpVoted: false,
43+
isDownVoted: false,
44+
upVotes: 0,
45+
time: '1 minute ago',
46+
replies: []
47+
}
48+
]
49+
},
50+
{
51+
userImgSrc: 'https://picsum.photos/800',
52+
name: `user${i + 1}y`,
53+
commentId: `${i + 1}y`,
54+
comment: `this is the dummy reply which is replied by another${i}y`,
55+
isUpVoted: false,
56+
isDownVoted: false,
57+
upVotes: 0,
58+
time: '1 minute ago',
59+
replies: []
60+
},
61+
{
62+
userImgSrc: 'https://picsum.photos/800',
63+
name: `user${i + 1}y`,
64+
commentId: `${i + 1}y`,
65+
comment: `this is the dummy reply which is replied by another${i}y`,
66+
isUpVoted: false,
67+
isDownVoted: false,
68+
upVotes: 0,
69+
time: '1 minute ago',
70+
replies: []
71+
},
72+
{
73+
userImgSrc: 'https://picsum.photos/800',
74+
name: `user${i + 1}y`,
75+
commentId: `${i + 1}y`,
76+
comment: `this is the dummy reply which is replied by another${i}y`,
77+
isUpVoted: false,
78+
isDownVoted: false,
79+
upVotes: 0,
80+
time: '1 minute ago',
81+
replies: []
82+
}
83+
]
84+
}));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ComponentProps } from 'svelte';
2+
import { Comment } from '..';
3+
import { comments } from '$lib/dummyData';
4+
5+
export default {
6+
title: 'UI/Comment',
7+
component: Comment,
8+
tags: ['autodocs'],
9+
render: (args: { Component: Comment; props: ComponentProps<typeof Comment> }) => ({
10+
Component: Comment,
11+
props: args
12+
})
13+
};
14+
15+
export const Main = {
16+
args: {
17+
comment: comments[0]
18+
}
19+
};
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<script lang="ts">
2+
import { Like } from '$lib/icons';
3+
import { Avatar } from '$lib/ui';
4+
import { cn } from '$lib/utils';
5+
import type { HTMLAttributes } from 'svelte/elements';
6+
import type { CommentType } from '$lib/types';
7+
8+
interface ICommentProps extends HTMLAttributes<HTMLElement> {
9+
comment: CommentType;
10+
handleReply: () => void;
11+
}
12+
13+
let visibleReplies = $state(2);
14+
15+
const showMoreReplies = () => {
16+
visibleReplies = comment.replies.length;
17+
};
18+
19+
let { comment, handleReply, ...restProps }: ICommentProps = $props();
20+
</script>
21+
22+
<article {...restProps} class={cn([restProps.class].join(' '))}>
23+
<div class="align-start flex gap-2">
24+
<Avatar src={comment.userImgSrc} size="sm" />
25+
<div>
26+
<h3 class="font-semibold text-black">{comment.name}</h3>
27+
<p class="text-black-600 mt-0.5">{comment.comment}</p>
28+
</div>
29+
</div>
30+
<div class="ms-12 mt-2 flex items-center gap-2">
31+
<button
32+
onclick={() => {
33+
if (!comment.isUpVoted) {
34+
comment.upVotes++;
35+
comment.isUpVoted = true;
36+
comment.isDownVoted = false;
37+
}
38+
}}
39+
>
40+
<Like
41+
size="18px"
42+
color={comment.isUpVoted
43+
? 'var(--color-brand-burnt-orange)'
44+
: 'var(--color-black-600)'}
45+
fill={comment.isUpVoted
46+
? 'var(--color-brand-burnt-orange)'
47+
: 'var(--color-black-600)'}
48+
/>
49+
</button>
50+
<p class="text-black-600 font-semibold">{comment.upVotes}</p>
51+
<button
52+
onclick={() => {
53+
if (!comment.isDownVoted) {
54+
comment.upVotes--;
55+
comment.isDownVoted = true;
56+
comment.isUpVoted = false;
57+
}
58+
}}
59+
>
60+
<Like
61+
size="18px"
62+
color={comment.isDownVoted
63+
? 'var(--color-brand-burnt-orange)'
64+
: 'var(--color-black-600)'}
65+
fill={comment.isDownVoted
66+
? 'var(--color-brand-burnt-orange)'
67+
: 'var(--color-black-600)'}
68+
class="rotate-180"
69+
/>
70+
</button>
71+
<span class="bg-black-600 inline-block h-1 w-1 rounded-full"></span>
72+
<button onclick={handleReply} class="text-black-600 font-semibold">Reply</button>
73+
<span class="bg-black-600 inline-block h-1 w-1 rounded-full"></span>
74+
<p class="text-black-600">{comment.time}</p>
75+
</div>
76+
{#if comment?.replies?.length}
77+
<ul class="ms-12 mt-4 space-y-2">
78+
{#each comment.replies.slice(0, visibleReplies) as reply}
79+
<li>
80+
<div class="align-start flex gap-2">
81+
<Avatar src={reply.userImgSrc} size="sm" />
82+
<div>
83+
<h3 class="font-semibold text-black">{reply.name}</h3>
84+
<p class="text-black-600 mt-0.5">{reply.comment}</p>
85+
</div>
86+
</div>
87+
<div class="ms-12 mt-2 flex items-center gap-2">
88+
<button
89+
onclick={() => {
90+
if (!reply.isUpVoted) {
91+
reply.upVotes++;
92+
reply.isUpVoted = true;
93+
reply.isDownVoted = false;
94+
}
95+
}}
96+
>
97+
<Like
98+
size="18px"
99+
color={reply.isUpVoted
100+
? 'var(--color-brand-burnt-orange)'
101+
: 'var(--color-black-600)'}
102+
fill={reply.isUpVoted
103+
? 'var(--color-brand-burnt-orange)'
104+
: 'var(--color-black-600)'}
105+
/>
106+
</button>
107+
<p class="text-black-600 font-semibold">{reply.upVotes}</p>
108+
<button
109+
onclick={() => {
110+
if (!reply.isDownVoted) {
111+
reply.upVotes--;
112+
reply.isDownVoted = true;
113+
reply.isUpVoted = false;
114+
}
115+
}}
116+
>
117+
<Like
118+
size="18px"
119+
color={reply.isDownVoted
120+
? 'var(--color-brand-burnt-orange)'
121+
: 'var(--color-black-600)'}
122+
fill={reply.isDownVoted
123+
? 'var(--color-brand-burnt-orange)'
124+
: 'var(--color-black-600)'}
125+
class="rotate-180"
126+
/>
127+
</button>
128+
<span class="bg-black-600 inline-block h-1 w-1 rounded-full"></span>
129+
<button onclick={handleReply} class="text-black-600 font-semibold"
130+
>Reply</button
131+
>
132+
<span class="bg-black-600 inline-block h-1 w-1 rounded-full"></span>
133+
<p class="text-black-600">{reply.time}</p>
134+
</div>
135+
</li>
136+
{/each}
137+
{#if comment.replies.length > visibleReplies}
138+
<button
139+
onclick={showMoreReplies}
140+
class="text-brand-burnt-orange mt-1 text-sm font-medium"
141+
>
142+
See {comment.replies.length - visibleReplies} more replies
143+
</button>
144+
{/if}
145+
</ul>
146+
{/if}
147+
</article>

platforms/metagram/src/lib/fragments/Drawer/Drawer.svelte

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,21 @@
77
import type { SwipeCustomEvent } from 'svelte-gestures';
88
99
interface IDrawerProps extends HTMLAttributes<HTMLDivElement> {
10-
isPaneOpen?: boolean;
10+
drawer?: CupertinoPane;
1111
children?: Snippet;
12-
handleSwipe?: (isOpen: boolean | undefined) => void;
1312
}
1413
15-
let {
16-
isPaneOpen = $bindable(),
17-
children = undefined,
18-
handleSwipe,
19-
...restProps
20-
}: IDrawerProps = $props();
14+
let { drawer = $bindable(), children = undefined, ...restProps }: IDrawerProps = $props();
2115
2216
let drawerElement: HTMLElement;
23-
let drawer: CupertinoPane;
2417
2518
function dismiss() {
2619
if (drawer) drawer.destroy({ animate: true });
2720
}
2821
2922
const handleDrawerSwipe = (event: SwipeCustomEvent) => {
3023
if (event.detail.direction === ('down' as string)) {
31-
handleSwipe?.(false);
3224
drawer?.destroy({ animate: true });
33-
isPaneOpen = false;
3425
}
3526
};
3627
@@ -51,13 +42,6 @@
5142
onBackdropTap: () => dismiss()
5243
}
5344
});
54-
if (isPaneOpen) {
55-
drawer.present({ animate: true });
56-
} else {
57-
drawer.destroy({ animate: true });
58-
}
59-
60-
drawer.present();
6145
});
6246
</script>
6347

@@ -71,7 +55,9 @@
7155
onswipe={handleDrawerSwipe}
7256
class={cn(restProps.class)}
7357
>
74-
{@render children?.()}
58+
<div class="h-[100%] overflow-y-scroll">
59+
{@render children?.()}
60+
</div>
7561
</div>
7662

7763
<style>

platforms/metagram/src/lib/fragments/MessageInput/MessageInput.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
interface IMessageInputProps extends HTMLAttributes<HTMLElement> {
99
variant: 'comment' | 'dm';
10+
input?: HTMLInputElement;
1011
src: string;
1112
value: string;
1213
placeholder?: string;
@@ -18,7 +19,8 @@
1819
let {
1920
variant = 'comment',
2021
src = 'https://www.gravatar.com/avatar/2c7d99fe281ecd3bcd65ab915bac6dd5?s=250',
21-
value,
22+
value = $bindable(),
23+
input = $bindable(),
2224
placeholder,
2325
files = $bindable(),
2426
handleAdd,
@@ -46,7 +48,7 @@
4648
<HugeiconsIcon size="24px" icon={PlusSignIcon} color="var(--color-black-400)" />
4749
</button>
4850
{/if}
49-
<Input type="text" bind:value {placeholder} />
51+
<Input type="text" bind:input bind:value {placeholder} />
5052
{#if value || variant === 'dm'}
5153
<!-- svelte-ignore a11y_click_events_have_key_events -->
5254
<!-- svelte-ignore a11y_no_static_element_interactions -->

platforms/metagram/src/lib/fragments/Post/Post.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@
8282
</button>
8383
</div>
8484
<div class="flex items-center justify-between gap-3 text-lg text-black/40">
85-
<p>{count.likes} likes</p>
85+
<p class="subtext text-black-400">{count.likes} likes</p>
8686
<HugeiconsIcon
8787
icon={RecordIcon}
8888
size={5}
8989
strokeWidth={30}
9090
color="var(--color-black-400)"
9191
className="rounded-full"
9292
/>
93-
<p>{count.comments} comments</p>
93+
<p class="subtext text-black-400">{count.comments} comments</p>
9494
</div>
9595
</div>
9696
</article>

platforms/metagram/src/lib/fragments/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export { default as SideBar } from './SideBar/SideBar.svelte';
1111
export { default as RightAside } from './RightAside/RightAside.svelte';
1212
export { default as SettingsToggleButton } from './SettingsToggleButton/SettingsToggleButton.svelte';
1313
export { default as Post } from './Post/Post.svelte';
14+
export { default as Comment } from './Comment/Comment.svelte';
1415
export { default as SettingsDeleteButton } from './SettingsDeleteButton/SettingsDeleteButton.svelte';

platforms/metagram/src/lib/icons/Like.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<script lang="ts">
22
import type { ISvgProps } from './../types';
33
4-
let { size = '20px', color = '#A5A5A5', ...restProps }: ISvgProps = $props();
4+
let { size = '20px', color = '#A5A5A5', fill, ...restProps }: ISvgProps = $props();
55
</script>
66

77
<svg
88
width={size}
99
height={size}
1010
viewBox="0 0 20 20"
11-
fill="none"
11+
{fill}
1212
xmlns="http://www.w3.org/2000/svg"
1313
{...restProps}
1414
>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export const isNavigatingThroughNav = $state({
22
value: false
33
});
4+
5+
export const showComments = $state({
6+
value: false
7+
});

0 commit comments

Comments
 (0)