Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}
},
"[svelte]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
Expand Down
35 changes: 35 additions & 0 deletions platforms/metagram/src/lib/ui/Message/Message.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ComponentProps } from "svelte";
import Message from "./Message.svelte";

export default {
title: "UI/Message",
component: Message,
tags: ["autodocs"],
render: (args: {
Component: Message;
props: ComponentProps<typeof Message>;
}) => ({
Component: Message,
props: args,
}),
};

export const Primary = {
args: {
avatar: "https://www.gravatar.com/avatar/2c7d99fe281ecd3bcd65ab915bac6dd5?s=250",
username: "donaldthefirstt",
text: "i was thinking of making it to the conference so we could take some more fire pictures like last time",
unread: false,
callback: () => alert("Message clicked"),
},
};

export const Unread = {
args: {
avatar: "https://www.gravatar.com/avatar/2c7d99fe281ecd3bcd65ab915bac6dd5?s=250",
username: "donaldthefirstt",
text: "i was thinking of making it to the conference so we could take some more fire pictures like last time",
unread: true,
callback: () => alert("Message clicked"),
},
};
68 changes: 68 additions & 0 deletions platforms/metagram/src/lib/ui/Message/Message.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
import { Avatar } from '$lib/ui';
import { cn } from '$lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential circular dependency in imports.

You're importing Avatar from $lib/ui while this component is being added to the same UI library (as seen in the index.ts changes). Consider importing Avatar directly from its path to avoid circular dependencies.

-import { Avatar } from '$lib/ui';
+import Avatar from '../Avatar/Avatar.svelte';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Avatar } from '$lib/ui';
import { cn } from '$lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
import Avatar from '../Avatar/Avatar.svelte';
import { cn } from '$lib/utils';
import type { HTMLAttributes } from 'svelte/elements';


interface IMessageProps extends HTMLAttributes<HTMLButtonElement> {
avatar: string;
username: string;
text: string;
unread?: boolean;
callback: () => void;
}

const {
avatar,
username,
text,
unread = false,
callback,
...restProps
}: IMessageProps = $props();

const messageText = $derived(text.length < 80 ? text : `${text.substring(0, 80)}...`);
</script>

<button
{...restProps}
class={cn([
'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-4 hover:bg-gray-100',
restProps.class
])}
onclick={callback}
>
<Avatar src={avatar} alt="User Avatar" size="md" />
<span class="flex w-full flex-col items-start justify-end gap-1">
<span class="flex w-full items-center justify-between">
<h2>{username}</h2>
{#if unread}
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
{/if}
</span>
<p class="text-black/60">{messageText}</p>
</span>
</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add accessibility improvements.

The message component could benefit from improved accessibility:

  1. Add an appropriate ARIA role
  2. Include an ARIA label to describe the purpose of the button
  3. Ensure color contrast meets WCAG standards (especially for the "text-black/60" text)
<button
	{...restProps}
	class={cn([
		'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-4 hover:bg-gray-100',
		restProps.class
	])}
-	onclick={callback}
+	on:click={callback}
+	role="button"
+	aria-label={`Message from ${username}`}
>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
{...restProps}
class={cn([
'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-4 hover:bg-gray-100',
restProps.class
])}
onclick={callback}
>
<Avatar src={avatar} alt="User Avatar" size="md" />
<span class="flex w-full flex-col items-start justify-end gap-1">
<span class="flex w-full items-center justify-between">
<h2>{username}</h2>
{#if unread}
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
{/if}
</span>
<p class="text-black/60">{messageText}</p>
</span>
</button>
<button
{...restProps}
class={cn([
'relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-4 hover:bg-gray-100',
restProps.class
])}
on:click={callback}
role="button"
aria-label={`Message from ${username}`}
>
<Avatar src={avatar} alt="User Avatar" size="md" />
<span class="flex w-full flex-col items-start justify-end gap-1">
<span class="flex w-full items-center justify-between">
<h2>{username}</h2>
{#if unread}
<span class="h-2 w-2 rounded-full bg-blue-500"></span>
{/if}
</span>
<p class="text-black/60">{messageText}</p>
</span>
</button>


<!--
@component
@name Message
@description A message component that displays a user's avatar, username, and message text. It also includes an optional unread indicator.
@props
- avatar: string - The URL of the user's avatar image.
- username: string - The name of the user.
- text: string - The message text.
- unread: boolean - Optional. Indicates if the message is unread. Defaults to false.
- callback: () => void - Function to call when the message is clicked.
@usage
<script>
import { Message } from '$lib/ui';
</script>

<Message
avatar="https://example.com/avatar.jpg"
username="John Doe"
text="Hello, how are you?"
unread={true}
callback={() => console.log('Message clicked')}
/>
-->
1 change: 1 addition & 0 deletions platforms/metagram/src/lib/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as Button } from "./Button/Button.svelte";
export { default as Avatar } from "./Avatar/Avatar.svelte";
export { default as Message } from "./Message/Message.svelte";
Loading