forked from 409/chat-app-frontend
message timestamps + client avatars
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { IMessage } from '$lib/message.svelte';
|
||||
import { toSvg } from 'jdenticon';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(utc);
|
||||
|
||||
interface Props {
|
||||
message: IMessage;
|
||||
@@ -7,13 +15,40 @@
|
||||
}
|
||||
|
||||
let { message, localSenderId }: Props = $props();
|
||||
|
||||
let isLocalMessage = $derived(message.senderId === localSenderId);
|
||||
let timestamp = $derived(dayjs.utc(message.timestamp));
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={[
|
||||
'bg-accent text-accent-foreground w-fit max-w-[70%] rounded-lg px-4 py-2',
|
||||
message.senderId === localSenderId && 'self-end bg-primary text-primary-foreground'
|
||||
]}
|
||||
class={['flex w-fit max-w-[70%] flex-row gap-2', isLocalMessage && 'flex-row-reverse self-end']}
|
||||
>
|
||||
<p>{message.content}</p>
|
||||
<Tooltip.Provider delayDuration={0}>
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger class="h-fit self-end">
|
||||
<div class="self-end overflow-hidden rounded-full border-2">
|
||||
{@html toSvg(message.senderId.toString(), 32)}
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>Client {message.senderId}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
|
||||
<div
|
||||
class={[
|
||||
'bg-accent text-accent-foreground rounded-lg px-4 py-2',
|
||||
isLocalMessage && 'bg-primary text-primary-foreground'
|
||||
]}
|
||||
>
|
||||
<p>{message.content}</p>
|
||||
<p class={['text-sm opacity-80', isLocalMessage && 'text-right']}>
|
||||
{#if timestamp.isSame(dayjs(), 'date')}
|
||||
{timestamp.format('HH:mm')}
|
||||
{:else}
|
||||
{timestamp.format('DD.MM.YYYY HH:mm')}
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
21
src/lib/components/ui/tooltip/index.ts
Normal file
21
src/lib/components/ui/tooltip/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import Trigger from "./tooltip-trigger.svelte";
|
||||
import Content from "./tooltip-content.svelte";
|
||||
|
||||
const Root = TooltipPrimitive.Root;
|
||||
const Provider = TooltipPrimitive.Provider;
|
||||
const Portal = TooltipPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Trigger,
|
||||
Content,
|
||||
Provider,
|
||||
Portal,
|
||||
//
|
||||
Root as Tooltip,
|
||||
Content as TooltipContent,
|
||||
Trigger as TooltipTrigger,
|
||||
Provider as TooltipProvider,
|
||||
Portal as TooltipPortal,
|
||||
};
|
||||
47
src/lib/components/ui/tooltip/tooltip-content.svelte
Normal file
47
src/lib/components/ui/tooltip/tooltip-content.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 0,
|
||||
side = "top",
|
||||
children,
|
||||
arrowClasses,
|
||||
...restProps
|
||||
}: TooltipPrimitive.ContentProps & {
|
||||
arrowClasses?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="tooltip-content"
|
||||
{sideOffset}
|
||||
{side}
|
||||
class={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-tooltip-content-transform-origin) z-50 w-fit text-balance rounded-md px-3 py-1.5 text-xs",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<TooltipPrimitive.Arrow>
|
||||
{#snippet child({ props })}
|
||||
<div
|
||||
class={cn(
|
||||
"bg-primary z-50 size-2.5 rotate-45 rounded-[2px]",
|
||||
"data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]",
|
||||
"data-[side=bottom]:-translate-y-[calc(-50%_+_1px)] data-[side=bottom]:translate-x-1/2",
|
||||
"data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2",
|
||||
"data-[side=left]:translate-y-[calc(50%_-_3px)]",
|
||||
arrowClasses
|
||||
)}
|
||||
{...props}
|
||||
></div>
|
||||
{/snippet}
|
||||
</TooltipPrimitive.Arrow>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
7
src/lib/components/ui/tooltip/tooltip-trigger.svelte
Normal file
7
src/lib/components/ui/tooltip/tooltip-trigger.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
||||
@@ -3,6 +3,7 @@ import { getContext, setContext } from "svelte";
|
||||
export interface IMessage {
|
||||
senderId: number;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export class MessageStore {
|
||||
|
||||
@@ -42,6 +42,7 @@ export class ChatSocket {
|
||||
}
|
||||
|
||||
const message: IMessage = JSON.parse(e.data);
|
||||
console.log(message);
|
||||
this.#store.addMessage(message);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user