Redesign entry summary view and more importantly rearchitected that awful state management

This commit is contained in:
2026-02-11 20:51:45 +13:00
parent 31bfbffb4d
commit e978d0df97
15 changed files with 358 additions and 20 deletions

View File

@@ -1,24 +1,36 @@
<script lang="ts">
import { generateHTML } from "@tiptap/html";
import * as Item from "$lib/components/ui/item/index.js";
import { CalendarDate, parseAbsolute } from "@internationalized/date";
import { marked } from 'marked';
import { StarterKit } from "@tiptap/starter-kit";
import { formatDate } from "$lib/date.ts";
const { entry, clickCB } = $props();
let {
entry,
value = $bindable<CalendarDate | undefined>(
new CalendarDate(2026, 2, 1),
),
} = $props()
const formattedDate = formatDate(entry.date);
function select() { value = parseAbsolute(entry.date) }
</script>
<button class="bg-white/10 p-4 rounded-xl w-full hover:brightness-80 transition-all mb-3" onclick={clickCB}>
<div class="flex flex-row">
<div class="text-start">
<p class="text-sm text-white/60">{formattedDate}</p>
{@html marked(entry.content)}
</div>
<img
class="max-h-30 min-h-15 max-w-1/2 rounded-xl object-cover ml-auto"
src={entry.image}
alt=""
/>
</div>
<button class="border border-white/30 rounded-xl w-full hover:brightness-80 transition-all mb-3" onclick={select}>
<Item.Root>
<Item.Content class="text-left">
<Item.Description>{formattedDate}</Item.Description>
<Item.Title>{@html marked(entry.content)}</Item.Title>
</Item.Content>
<Item.Media>
<img
class="max-h-30 min-h-15 max-w-1/2 rounded-xl object-cover ml-auto"
src={entry.image}
alt=""
/>
</Item.Media>
</Item.Root>
</button>

View File

@@ -0,0 +1,34 @@
import Root from "./item.svelte";
import Group from "./item-group.svelte";
import Separator from "./item-separator.svelte";
import Header from "./item-header.svelte";
import Footer from "./item-footer.svelte";
import Content from "./item-content.svelte";
import Title from "./item-title.svelte";
import Description from "./item-description.svelte";
import Actions from "./item-actions.svelte";
import Media from "./item-media.svelte";
export {
Root,
Group,
Separator,
Header,
Footer,
Content,
Title,
Description,
Actions,
Media,
//
Root as Item,
Group as ItemGroup,
Separator as ItemSeparator,
Header as ItemHeader,
Footer as ItemFooter,
Content as ItemContent,
Title as ItemTitle,
Description as ItemDescription,
Actions as ItemActions,
Media as ItemMedia,
};

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="item-actions"
class={cn("flex items-center gap-2", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="item-content"
class={cn("flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
</script>
<p
bind:this={ref}
data-slot="item-description"
class={cn(
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...restProps}
>
{@render children?.()}
</p>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="item-footer"
class={cn("flex basis-full items-center justify-between gap-2", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
role="list"
data-slot="item-group"
class={cn("group/item-group flex flex-col", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="item-header"
class={cn("flex basis-full items-center justify-between gap-2", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,42 @@
<script lang="ts" module>
import { tv, type VariantProps } from "tailwind-variants";
export const itemMediaVariants = tv({
base: "flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none",
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted size-8 rounded-sm border [&_svg:not([class*='size-'])]:size-4",
image: "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
},
},
defaultVariants: {
variant: "default",
},
});
export type ItemMediaVariant = VariantProps<typeof itemMediaVariants>["variant"];
</script>
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
variant = "default",
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { variant?: ItemMediaVariant } = $props();
</script>
<div
bind:this={ref}
data-slot="item-media"
data-variant={variant}
class={cn(itemMediaVariants({ variant }), className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { Separator } from "$lib/components/ui/separator/index.js";
import { cn } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
...restProps
}: ComponentProps<typeof Separator> = $props();
</script>
<Separator
bind:ref
data-slot="item-separator"
orientation="horizontal"
class={cn("my-0", className)}
{...restProps}
/>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="item-title"
class={cn("flex w-fit items-center gap-2 text-sm leading-snug font-medium", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,60 @@
<script lang="ts" module>
import { tv, type VariantProps } from "tailwind-variants";
export const itemVariants = tv({
base: "group/item [a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 flex flex-wrap items-center rounded-md border border-transparent text-sm transition-colors duration-100 outline-none focus-visible:ring-[3px] [a]:transition-colors",
variants: {
variant: {
default: "bg-transparent",
outline: "border-border",
muted: "bg-muted/50",
},
size: {
default: "gap-4 p-4",
sm: "gap-2.5 px-4 py-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export type ItemSize = VariantProps<typeof itemVariants>["size"];
export type ItemVariant = VariantProps<typeof itemVariants>["variant"];
</script>
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
import type { Snippet } from "svelte";
let {
ref = $bindable(null),
class: className,
child,
variant,
size,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
child?: Snippet<[{ props: Record<string, unknown> }]>;
variant?: ItemVariant;
size?: ItemSize;
} = $props();
const mergedProps = $derived({
class: cn(itemVariants({ variant, size }), className),
"data-slot": "item",
"data-variant": variant,
"data-size": size,
...restProps,
});
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div bind:this={ref} {...mergedProps}>
{@render mergedProps.children?.()}
</div>
{/if}

View File

@@ -0,0 +1,7 @@
import Root from "./separator.svelte";
export {
Root,
//
Root as Separator,
};

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { Separator as SeparatorPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
"data-slot": dataSlot = "separator",
...restProps
}: SeparatorPrimitive.RootProps = $props();
</script>
<SeparatorPrimitive.Root
bind:ref
data-slot={dataSlot}
class={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:min-h-full data-[orientation=vertical]:w-px",
className
)}
{...restProps}
/>

View File

@@ -16,8 +16,6 @@
let dateValue = $state(new CalendarDate(2026, 2, 1));
let selectEntry = async (hasEntry: boolean, data: string | null) => {
// data should be the entryID if hasEntry == true, or the date if there is no entry
if (hasEntry) {
@@ -51,11 +49,11 @@
<div class="md:w-1/2 space-y-4 md:mr-4">
<Calendar bind:value={dateValue} />
<ul>
<ul class="space-y-4">
{#each data.all as entry}
<EntrySummaryView
clickCB={async () => await selectEntry(true, entry.id)}
{entry}
bind:value={dateValue}
/>
{/each}
</ul>