Redesign entry summary view and more importantly rearchitected that awful state management
This commit is contained in:
@@ -1,24 +1,36 @@
|
|||||||
<script lang="ts">
|
<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 { marked } from 'marked';
|
||||||
import { StarterKit } from "@tiptap/starter-kit";
|
|
||||||
import { formatDate } from "$lib/date.ts";
|
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);
|
const formattedDate = formatDate(entry.date);
|
||||||
|
|
||||||
|
function select() { value = parseAbsolute(entry.date) }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="bg-white/10 p-4 rounded-xl w-full hover:brightness-80 transition-all mb-3" onclick={clickCB}>
|
<button class="border border-white/30 rounded-xl w-full hover:brightness-80 transition-all mb-3" onclick={select}>
|
||||||
<div class="flex flex-row">
|
<Item.Root>
|
||||||
<div class="text-start">
|
<Item.Content class="text-left">
|
||||||
<p class="text-sm text-white/60">{formattedDate}</p>
|
<Item.Description>{formattedDate}</Item.Description>
|
||||||
{@html marked(entry.content)}
|
<Item.Title>{@html marked(entry.content)}</Item.Title>
|
||||||
</div>
|
</Item.Content>
|
||||||
<img
|
|
||||||
class="max-h-30 min-h-15 max-w-1/2 rounded-xl object-cover ml-auto"
|
<Item.Media>
|
||||||
src={entry.image}
|
<img
|
||||||
alt=""
|
class="max-h-30 min-h-15 max-w-1/2 rounded-xl object-cover ml-auto"
|
||||||
/>
|
src={entry.image}
|
||||||
</div>
|
alt=""
|
||||||
|
/>
|
||||||
|
</Item.Media>
|
||||||
|
</Item.Root>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
34
src/lib/components/ui/item/index.ts
Normal file
34
src/lib/components/ui/item/index.ts
Normal 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,
|
||||||
|
};
|
||||||
20
src/lib/components/ui/item/item-actions.svelte
Normal file
20
src/lib/components/ui/item/item-actions.svelte
Normal 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>
|
||||||
20
src/lib/components/ui/item/item-content.svelte
Normal file
20
src/lib/components/ui/item/item-content.svelte
Normal 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>
|
||||||
24
src/lib/components/ui/item/item-description.svelte
Normal file
24
src/lib/components/ui/item/item-description.svelte
Normal 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>
|
||||||
20
src/lib/components/ui/item/item-footer.svelte
Normal file
20
src/lib/components/ui/item/item-footer.svelte
Normal 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>
|
||||||
21
src/lib/components/ui/item/item-group.svelte
Normal file
21
src/lib/components/ui/item/item-group.svelte
Normal 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>
|
||||||
20
src/lib/components/ui/item/item-header.svelte
Normal file
20
src/lib/components/ui/item/item-header.svelte
Normal 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>
|
||||||
42
src/lib/components/ui/item/item-media.svelte
Normal file
42
src/lib/components/ui/item/item-media.svelte
Normal 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>
|
||||||
19
src/lib/components/ui/item/item-separator.svelte
Normal file
19
src/lib/components/ui/item/item-separator.svelte
Normal 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}
|
||||||
|
/>
|
||||||
20
src/lib/components/ui/item/item-title.svelte
Normal file
20
src/lib/components/ui/item/item-title.svelte
Normal 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>
|
||||||
60
src/lib/components/ui/item/item.svelte
Normal file
60
src/lib/components/ui/item/item.svelte
Normal 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}
|
||||||
7
src/lib/components/ui/separator/index.ts
Normal file
7
src/lib/components/ui/separator/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./separator.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Separator,
|
||||||
|
};
|
||||||
21
src/lib/components/ui/separator/separator.svelte
Normal file
21
src/lib/components/ui/separator/separator.svelte
Normal 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}
|
||||||
|
/>
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
let dateValue = $state(new CalendarDate(2026, 2, 1));
|
let dateValue = $state(new CalendarDate(2026, 2, 1));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let selectEntry = async (hasEntry: boolean, data: string | null) => {
|
let selectEntry = async (hasEntry: boolean, data: string | null) => {
|
||||||
// data should be the entryID if hasEntry == true, or the date if there is no entry
|
// data should be the entryID if hasEntry == true, or the date if there is no entry
|
||||||
if (hasEntry) {
|
if (hasEntry) {
|
||||||
@@ -51,11 +49,11 @@
|
|||||||
|
|
||||||
<div class="md:w-1/2 space-y-4 md:mr-4">
|
<div class="md:w-1/2 space-y-4 md:mr-4">
|
||||||
<Calendar bind:value={dateValue} />
|
<Calendar bind:value={dateValue} />
|
||||||
<ul>
|
<ul class="space-y-4">
|
||||||
{#each data.all as entry}
|
{#each data.all as entry}
|
||||||
<EntrySummaryView
|
<EntrySummaryView
|
||||||
clickCB={async () => await selectEntry(true, entry.id)}
|
|
||||||
{entry}
|
{entry}
|
||||||
|
bind:value={dateValue}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user