mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 06:23:37 -06:00
feat: implement relative time feedback and improve tooltip customization
This commit is contained in:
@@ -1,34 +1,75 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export interface SearchMeta {
|
import { onMount } from "svelte";
|
||||||
totalCount: number;
|
import SimpleTooltip from "$lib/components/SimpleTooltip.svelte";
|
||||||
durationMs: number;
|
import { relativeTime } from "$lib/time";
|
||||||
timestamp: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { meta }: { meta: SearchMeta | null } = $props();
|
export interface SearchMeta {
|
||||||
|
totalCount: number;
|
||||||
|
durationMs: number;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
let formattedTime = $derived(
|
let { meta }: { meta: SearchMeta | null } = $props();
|
||||||
meta
|
|
||||||
? meta.timestamp.toLocaleTimeString(undefined, {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
})
|
|
||||||
: ""
|
|
||||||
);
|
|
||||||
|
|
||||||
let countLabel = $derived(meta ? meta.totalCount.toLocaleString() : "");
|
let now = $state(new Date());
|
||||||
let resultNoun = $derived(meta ? (meta.totalCount !== 1 ? "results" : "result") : "");
|
|
||||||
let durationLabel = $derived(meta ? `${Math.round(meta.durationMs)}ms` : "");
|
let formattedTime = $derived(
|
||||||
|
meta
|
||||||
|
? meta.timestamp.toLocaleTimeString(undefined, {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
})
|
||||||
|
: "",
|
||||||
|
);
|
||||||
|
|
||||||
|
let relativeTimeResult = $derived(
|
||||||
|
meta ? relativeTime(meta.timestamp, now) : null,
|
||||||
|
);
|
||||||
|
let relativeTimeText = $derived(relativeTimeResult?.text ?? "");
|
||||||
|
|
||||||
|
let countLabel = $derived(meta ? meta.totalCount.toLocaleString() : "");
|
||||||
|
let resultNoun = $derived(
|
||||||
|
meta ? (meta.totalCount !== 1 ? "results" : "result") : "",
|
||||||
|
);
|
||||||
|
let durationLabel = $derived(
|
||||||
|
meta ? `${Math.round(meta.durationMs)}ms` : "",
|
||||||
|
);
|
||||||
|
|
||||||
|
let tooltipText = $derived(
|
||||||
|
meta ? `${relativeTimeText} · ${formattedTime}` : "",
|
||||||
|
);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let nowTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function scheduleNowTick() {
|
||||||
|
const delay = relativeTimeResult?.nextUpdateMs ?? 1000;
|
||||||
|
nowTimeoutId = setTimeout(() => {
|
||||||
|
now = new Date();
|
||||||
|
scheduleNowTick();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
scheduleNowTick();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (nowTimeoutId) clearTimeout(nowTimeoutId);
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if meta}
|
<SimpleTooltip
|
||||||
<p
|
text={tooltipText}
|
||||||
class="pl-1 text-xs"
|
contentClass="whitespace-nowrap text-[12px] px-2 py-1"
|
||||||
title="Last searched at {formattedTime}"
|
triggerClass="self-start"
|
||||||
>
|
sideOffset={0}
|
||||||
<span class="text-muted-foreground/70">{countLabel}</span>
|
>
|
||||||
<span class="text-muted-foreground/35">{resultNoun} in</span>
|
<span
|
||||||
<span class="text-muted-foreground/70">{durationLabel}</span>
|
class="pl-1 text-xs transition-opacity duration-200"
|
||||||
</p>
|
style:opacity={meta ? 1 : 0}
|
||||||
{/if}
|
>
|
||||||
|
<span class="text-muted-foreground/70">{countLabel}</span>
|
||||||
|
<span class="text-muted-foreground/35">{resultNoun} in</span>
|
||||||
|
<span class="text-muted-foreground/70">{durationLabel}</span>
|
||||||
|
</span>
|
||||||
|
</SimpleTooltip>
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Tooltip } from "bits-ui";
|
import { Tooltip } from "bits-ui";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
text,
|
text,
|
||||||
delay = 150,
|
delay = 150,
|
||||||
side = "top",
|
side = "top",
|
||||||
passthrough = false,
|
passthrough = false,
|
||||||
|
triggerClass = "",
|
||||||
|
contentClass = "",
|
||||||
|
sideOffset = 6,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
text: string;
|
text: string;
|
||||||
delay?: number;
|
delay?: number;
|
||||||
side?: "top" | "bottom" | "left" | "right";
|
side?: "top" | "bottom" | "left" | "right";
|
||||||
passthrough?: boolean;
|
passthrough?: boolean;
|
||||||
|
triggerClass?: string;
|
||||||
|
contentClass?: string;
|
||||||
|
sideOffset?: number;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tooltip.Root delayDuration={delay} disableHoverableContent={passthrough}>
|
<Tooltip.Root delayDuration={delay} disableHoverableContent={passthrough}>
|
||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
{@render children()}
|
{#snippet child({ props })}
|
||||||
|
<span class={triggerClass} {...props}>
|
||||||
|
{@render children()}
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Content
|
<Tooltip.Content
|
||||||
{side}
|
{side}
|
||||||
sideOffset={6}
|
{sideOffset}
|
||||||
class="z-50 bg-card text-card-foreground text-xs border border-border rounded-md px-2.5 py-1.5 shadow-md whitespace-pre-line max-w-max"
|
class={cn("z-50 bg-card text-card-foreground text-xs border border-border rounded-md px-2.5 py-1.5 shadow-sm whitespace-pre-line max-w-max text-left", contentClass)}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
|
|||||||
Reference in New Issue
Block a user