Skip to content

Commit 6821d98

Browse files
authored
feat: messages (#174)
* feat: messages * feat: ChatMessae * feat: messages by id * fix: messages page * fix: icon name * fix: hide bottom nav for chat * fix: header * fix: message bubble * fix: message bubble * fix: message bubble * fix: as per suggestion * fix: messaging
1 parent f446256 commit 6821d98

File tree

12 files changed

+256
-51
lines changed

12 files changed

+256
-51
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { ComponentProps } from 'svelte';
2+
import { ChatMessage } from '..';
3+
4+
export default {
5+
title: 'UI/ChatMessage',
6+
component: ChatMessage,
7+
tags: ['autodocs'],
8+
render: (args: { Component: ChatMessage; props: ComponentProps<typeof ChatMessage> }) => ({
9+
Component: ChatMessage,
10+
props: args
11+
})
12+
};
13+
14+
export const Outgoing = {
15+
args: {
16+
isOwn: true
17+
}
18+
};
19+
20+
export const Incoming = {
21+
args: {
22+
isOwn: false,
23+
message:
24+
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sed voluptatem accusantium voluptas vel, libero minus veniam at! Doloribus autem, id, ipsum laudantium dolor blanditiis nulla eum eveniet illo perspiciatis iusto.Voluptas ea pariatur eveniet quidem incidunt vitae sunt, hic labore nisi officiis consectetur autem odio repellendus nesciunt quisquam alias consequatur corrupti quaerat, minus qui. Obcaecati deleniti optio quod quibusdam placeat.'
25+
}
26+
};
27+
28+
export const OutgoingWithoutHead = {
29+
args: {
30+
isOwn: true,
31+
isHeadNeeded: false
32+
}
33+
};
34+
35+
export const WithoutHead = {
36+
args: {
37+
isOwn: false,
38+
isHeadNeeded: false,
39+
message:
40+
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sed voluptatem accusantium voluptas vel, libero minus veniam at! Doloribus autem, id, ipsum laudantium dolor blanditiis nulla eum eveniet illo perspiciatis iusto.Voluptas ea pariatur eveniet quidem incidunt vitae sunt, hic labore nisi officiis consectetur autem odio repellendus nesciunt quisquam alias consequatur corrupti quaerat, minus qui. Obcaecati deleniti optio quod quibusdam placeat.'
41+
}
42+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<script lang="ts">
2+
import { Avatar } from '$lib/ui';
3+
import { cn } from '$lib/utils';
4+
import type { HTMLAttributes } from 'svelte/elements';
5+
6+
interface IChatMessageProps extends HTMLAttributes<HTMLElement> {
7+
userImgSrc: string;
8+
message: string;
9+
time: string;
10+
isOwn: boolean;
11+
isHeadNeeded?: boolean;
12+
}
13+
14+
let {
15+
userImgSrc = 'https://picsum.photos/id/237/200/300',
16+
message = 'i was thinking maybe like 12th?',
17+
time = '12:55 AM',
18+
isOwn,
19+
isHeadNeeded = true,
20+
...restProps
21+
}: IChatMessageProps = $props();
22+
</script>
23+
24+
<div
25+
{...restProps}
26+
class={cn(
27+
[`flex items-start gap-2 ${isOwn ? 'flex' : 'flex-row-reverse'}`, restProps.class].join(' ')
28+
)}
29+
>
30+
<div class="w-8 flex-shrink-0">
31+
{#if isHeadNeeded}
32+
<Avatar size="xs" src={userImgSrc} />
33+
{/if}
34+
</div>
35+
36+
<div class={cn(`max-w-[50%] ${isHeadNeeded ? 'mt-4' : 'mt-0'}`)}>
37+
<div
38+
class={cn(
39+
`relative rounded-3xl px-4 py-2 ${isOwn ? 'bg-grey' : 'bg-brand-burnt-orange'}`
40+
)}
41+
>
42+
{#if isHeadNeeded}
43+
<svg
44+
class={`absolute ${isOwn ? 'start-[-5px] top-[-2px]' : 'end-[-5px] top-[2px]'}`}
45+
width="22"
46+
height="17"
47+
viewBox="0 0 22 17"
48+
fill="none"
49+
xmlns="http://www.w3.org/2000/svg"
50+
>
51+
<path
52+
d="M0 0C5.79116 4.95613 8.40437 9.60298 10 17L22 2C11 2.5 7.53377 0.634763 0 0Z"
53+
fill={isOwn ? '#F5F5F5' : 'var(--color-brand-burnt-orange)'}
54+
/>
55+
</svg>
56+
{/if}
57+
58+
<p class={cn(`${!isOwn ? 'text-white' : 'text-black-600'}`)}>
59+
{message}
60+
</p>
61+
</div>
62+
63+
<p
64+
class={cn(
65+
`subtext text-black-400 mt-0.5 flex text-xs text-nowrap ${
66+
isOwn ? 'justify-end' : 'justify-start'
67+
}`
68+
)}
69+
>
70+
{time}
71+
</p>
72+
</div>
73+
</div>

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88
} from '@hugeicons/core-free-icons';
99
import { HugeiconsIcon } from '@hugeicons/svelte';
1010
import type { HTMLAttributes } from 'svelte/elements';
11+
import ActionMenu from '../ActionMenu/ActionMenu.svelte';
1112
1213
interface IHeaderProps extends HTMLAttributes<HTMLElement> {
1314
variant: 'primary' | 'secondary' | 'tertiary';
1415
heading?: string;
1516
callback?: () => void;
17+
options?: { name: string; handler: () => void }[];
1618
}
1719
18-
const { variant, callback, heading, ...restProps }: IHeaderProps = $props();
20+
const { variant, callback, heading, options, ...restProps }: IHeaderProps = $props();
1921
2022
const variantClasses = {
2123
primary: {
@@ -45,7 +47,7 @@
4547
4648
const classes = $derived({
4749
common: cn(
48-
'flex items-center justify-between my-4 pb-6 border-b-[1px] md:border-0 border-grey'
50+
'w-full flex items-center justify-between my-4 pb-6 border-b-[1px] md:border-0 border-grey'
4951
),
5052
text: variantClasses[variant].text,
5153
background: variantClasses[variant].background
@@ -86,6 +88,8 @@
8688
>
8789
<HugeiconsIcon icon={menuButton[variant]} size={24} color="var(--color-black-500)" />
8890
</button>
91+
{:else if variant === 'secondary' && options}
92+
<ActionMenu {options} />
8993
{/if}
9094
</header>
9195

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<button
2727
{...restProps}
2828
class={cn([
29-
'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-4 hover:bg-gray-100',
29+
'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-4',
3030
restProps.class
3131
])}
3232
onclick={callback}
@@ -39,7 +39,7 @@
3939
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
4040
{/if}
4141
</span>
42-
<p class="text-black/60">{messageText}</p>
42+
<p class="text-start text-black/60">{messageText}</p>
4343
</span>
4444
</button>
4545

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import { Avatar, Input } from '$lib/ui';
33
import { cn } from '$lib/utils';
4-
import { ImageCompositionOvalIcon, PlusSignIcon, SentIcon } from '@hugeicons/core-free-icons';
4+
import { PlusSignIcon, SentIcon } from '@hugeicons/core-free-icons';
55
import { HugeiconsIcon } from '@hugeicons/svelte';
66
import type { HTMLAttributes } from 'svelte/elements';
77
@@ -39,47 +39,30 @@
3939
{:else}
4040
<!-- svelte-ignore a11y_click_events_have_key_events -->
4141
<!-- svelte-ignore a11y_no_static_element_interactions -->
42+
<input
43+
id="add-image"
44+
type="file"
45+
class="hidden"
46+
accept="image/*"
47+
bind:files
48+
bind:this={fileInput}
49+
/>
4250
<button
4351
type="button"
4452
class="bg-grey flex aspect-square h-13 w-13 items-center justify-center rounded-full border-0 p-0"
45-
onclick={handleAdd}
46-
aria-label="Add attachment"
53+
aria-label="add-image"
54+
onclick={() => fileInput?.click()}
4755
>
4856
<HugeiconsIcon size="24px" icon={PlusSignIcon} color="var(--color-black-400)" />
4957
</button>
5058
{/if}
5159
<Input type="text" bind:input bind:value {placeholder} />
52-
{#if value || variant === 'dm'}
53-
<!-- svelte-ignore a11y_click_events_have_key_events -->
54-
<!-- svelte-ignore a11y_no_static_element_interactions -->
55-
<div
56-
class="bg-grey flex aspect-square h-13 w-13 items-center justify-center rounded-full"
57-
onclick={handleSend}
58-
>
59-
<HugeiconsIcon size="24px" icon={SentIcon} color="var(--color-black-400)" />
60-
</div>
61-
{:else}
62-
<div class="bg-grey flex aspect-square h-13 w-13 items-center justify-center rounded-full">
63-
<input
64-
id="add-image"
65-
type="file"
66-
class="hidden"
67-
accept="image/*"
68-
bind:files
69-
bind:this={fileInput}
70-
/>
71-
<button
72-
type="button"
73-
class="bg-grey flex aspect-square h-13 w-13 items-center justify-center rounded-full border-0 p-0"
74-
aria-label="add-image"
75-
onclick={() => fileInput?.click()}
76-
>
77-
<HugeiconsIcon
78-
size="24px"
79-
icon={ImageCompositionOvalIcon}
80-
color="var(--color-black-400)"
81-
/>
82-
</button>
83-
</div>
84-
{/if}
60+
<!-- svelte-ignore a11y_click_events_have_key_events -->
61+
<!-- svelte-ignore a11y_no_static_element_interactions -->
62+
<div
63+
class="bg-grey flex aspect-square h-13 w-13 items-center justify-center rounded-full"
64+
onclick={handleSend}
65+
>
66+
<HugeiconsIcon size="24px" icon={SentIcon} color="var(--color-black-400)" />
67+
</div>
8568
</div>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ 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 ChatMessage } from './ChatMessage/ChatMessage.svelte';
1415
export { default as Comment } from './Comment/Comment.svelte';
1516
export { default as SettingsDeleteButton } from './SettingsDeleteButton/SettingsDeleteButton.svelte';

platforms/metagram/src/lib/icons/Comment.svelte renamed to platforms/metagram/src/lib/icons/CommentIcon.svelte

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script lang="ts">
2-
import { Like, Refresh, Comment, Home, Flash, CommentsTwo, VerticalDots } from '.';
2+
import { Like, Refresh, CommentIcon, Home, Flash, CommentsTwo, VerticalDots } from '.';
33
</script>
44

55
<div class="flex flex-wrap items-center gap-2">
66
<Like />
77
<Refresh />
8-
<Comment />
8+
<CommentIcon />
99
<VerticalDots />
1010
<Home />
1111
<Flash />

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export { default as Icons } from './Icons.svelte';
22
export { default as Like } from './Like.svelte';
33
export { default as Refresh } from './Refresh.svelte';
4-
export { default as Comment } from './Comment.svelte';
4+
export { default as CommentIcon } from './CommentIcon.svelte';
55
export { default as VerticalDots } from './VerticalDots.svelte';
66
export { default as Home } from './Home.svelte';
77
export { default as Flash } from './Flash.svelte';

platforms/metagram/src/routes/(protected)/+layout.svelte

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import { goto } from '$app/navigation';
33
import { page } from '$app/state';
44
import { comments } from '$lib/dummyData';
5-
import { BottomNav, Header, Comment, MessageInput } from '$lib/fragments';
6-
import SideBar from '$lib/fragments/SideBar/SideBar.svelte';
5+
import { BottomNav, Header, Comment, MessageInput, SideBar } from '$lib/fragments';
76
import { Settings } from '$lib/icons';
87
import { showComments } from '$lib/store/store.svelte';
98
import type { CommentType } from '$lib/types';
@@ -15,6 +14,7 @@
1514
let commentInput: HTMLInputElement | undefined = $state();
1615
let _comments = $state(comments);
1716
let activeReplyToId: string | null = $state(null);
17+
let chatFriendId = $state();
1818
1919
const handleSend = async () => {
2020
const newComment = {
@@ -52,20 +52,22 @@
5252
};
5353
5454
$effect(() => {
55+
chatFriendId = page.params.id;
56+
5557
if (route.includes('home')) {
5658
heading = 'Feed';
5759
} else if (route.includes('discover')) {
5860
heading = 'Search';
5961
} else if (route.includes('post')) {
6062
heading = 'Post';
63+
} else if (route === `/messages/${chatFriendId}`) {
64+
heading = 'User Name';
6165
} else if (route.includes('messages')) {
6266
heading = 'Messages';
6367
} else if (route.includes('settings')) {
6468
heading = 'Settings';
6569
} else if (route.includes('profile')) {
6670
heading = 'Profile';
67-
} else {
68-
heading = '';
6971
}
7072
});
7173
</script>
@@ -74,9 +76,16 @@
7476
class={`block h-[100dvh] ${route !== '/home' ? 'grid-cols-[20vw_auto]' : 'grid-cols-[20vw_auto_30vw]'} md:grid`}
7577
>
7678
<SideBar profileSrc="https://picsum.photos/200" handlePost={async () => alert('adas')} />
77-
<section class="px-4 md:px-8 md:pt-8">
79+
<section class="hide-scrollbar h-[100dvh] overflow-y-auto px-4 pb-8 md:px-8 md:pt-8">
7880
<div class="flex items-center justify-between">
79-
<Header variant="primary" {heading} />
81+
<Header
82+
variant={route === `/messages/${chatFriendId}` ? 'secondary' : 'primary'}
83+
{heading}
84+
options={[
85+
{ name: 'Report', handler: () => alert('report') },
86+
{ name: 'Clear chat', handler: () => alert('clear') }
87+
]}
88+
/>
8089
{#if route === '/profile'}
8190
<div class="mb-6 flex md:hidden">
8291
<button
@@ -121,5 +130,8 @@
121130
{/if}
122131
</aside>
123132
{/if}
124-
<BottomNav profileSrc="https://picsum.photos/200" />
133+
134+
{#if route !== `/messages/${chatFriendId}`}
135+
<BottomNav class="btm-nav" profileSrc="https://picsum.photos/200" />
136+
{/if}
125137
</main>

0 commit comments

Comments
 (0)