summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/lib/components')
-rw-r--r--frontend/src/lib/components/AddButton.svelte6
-rw-r--r--frontend/src/lib/components/Badge.svelte2
-rw-r--r--frontend/src/lib/components/BookmarkButton.svelte10
-rw-r--r--frontend/src/lib/components/Card.svelte35
-rw-r--r--frontend/src/lib/components/Cardlet.svelte29
-rw-r--r--frontend/src/lib/components/DeleteButton.svelte15
-rw-r--r--frontend/src/lib/components/Dialog.svelte19
-rw-r--r--frontend/src/lib/components/Dropdown.svelte43
-rw-r--r--frontend/src/lib/components/Expander.svelte22
-rw-r--r--frontend/src/lib/components/Guard.svelte5
-rw-r--r--frontend/src/lib/components/Head.svelte3
-rw-r--r--frontend/src/lib/components/Labelled.svelte10
-rw-r--r--frontend/src/lib/components/LabelledBlock.svelte17
-rw-r--r--frontend/src/lib/components/OrganizedButton.svelte10
-rw-r--r--frontend/src/lib/components/RefreshButton.svelte10
-rw-r--r--frontend/src/lib/components/RemovePageButton.svelte8
-rw-r--r--frontend/src/lib/components/Select.svelte27
-rw-r--r--frontend/src/lib/components/Spinner.svelte4
-rw-r--r--frontend/src/lib/components/SubmitButton.svelte6
-rw-r--r--frontend/src/lib/components/Titlebar.svelte15
20 files changed, 197 insertions, 99 deletions
diff --git a/frontend/src/lib/components/AddButton.svelte b/frontend/src/lib/components/AddButton.svelte
index 9c0ab29..f07eafd 100644
--- a/frontend/src/lib/components/AddButton.svelte
+++ b/frontend/src/lib/components/AddButton.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- export let title: string;
+ let { title, onclick }: { title: string; onclick: () => void } = $props();
</script>
-<button class="btn-blue" {title} on:click>
- <span class="icon-base icon-[material-symbols--add]" />
+<button class="btn-blue" {title} aria-label={title} {onclick}>
+ <span class="icon-base icon-[material-symbols--add]"></span>
</button>
diff --git a/frontend/src/lib/components/Badge.svelte b/frontend/src/lib/components/Badge.svelte
index 7ad3173..6f8198a 100644
--- a/frontend/src/lib/components/Badge.svelte
+++ b/frontend/src/lib/components/Badge.svelte
@@ -2,7 +2,7 @@
import { fadeDefault } from '$lib/Transitions';
import { fade } from 'svelte/transition';
- export let number: number;
+ let { number }: { number: number } = $props();
</script>
{#if number > 0}
diff --git a/frontend/src/lib/components/BookmarkButton.svelte b/frontend/src/lib/components/BookmarkButton.svelte
index 89570e6..bdcbd75 100644
--- a/frontend/src/lib/components/BookmarkButton.svelte
+++ b/frontend/src/lib/components/BookmarkButton.svelte
@@ -1,9 +1,15 @@
<script lang="ts">
import Bookmark from '$lib/icons/Bookmark.svelte';
+ import type { MouseEventHandler } from 'svelte/elements';
- export let bookmarked: boolean;
+ interface Props {
+ bookmarked: boolean;
+ onclick: MouseEventHandler<HTMLButtonElement>;
+ }
+
+ let { bookmarked, onclick }: Props = $props();
</script>
-<button type="button" title="Toggle bookmark" class="flex text-base" on:click>
+<button type="button" title="Toggle bookmark" class="flex text-base" {onclick}>
<Bookmark hoverable {bookmarked} />
</button>
diff --git a/frontend/src/lib/components/Card.svelte b/frontend/src/lib/components/Card.svelte
index d209517..21181dc 100644
--- a/frontend/src/lib/components/Card.svelte
+++ b/frontend/src/lib/components/Card.svelte
@@ -1,4 +1,4 @@
-<script lang="ts" context="module">
+<script lang="ts" module>
import type { ComicFragment, ImageFragment } from '$gql/graphql';
interface CardDetails {
@@ -24,12 +24,29 @@
<script lang="ts">
import { src } from '$lib/Utils';
import Star from '$lib/icons/Star.svelte';
+ import type { Snippet } from 'svelte';
- export let href: string;
- export let details: CardDetails;
- export let compact = false;
- export let coverOnly = false;
- export let ellipsis = true;
+ interface Props {
+ href: string;
+ details: CardDetails;
+ compact?: boolean;
+ coverOnly?: boolean;
+ ellipsis?: boolean;
+ overlay?: Snippet;
+ children?: Snippet;
+ onclick?: (event: MouseEvent) => void;
+ }
+
+ let {
+ href,
+ details,
+ compact = false,
+ coverOnly = false,
+ ellipsis = true,
+ overlay,
+ children,
+ onclick
+ }: Props = $props();
</script>
<a
@@ -37,9 +54,9 @@
class="grid-card-v sm:grid-card-h focus-thick focus-blue relative grid overflow-hidden rounded bg-slate-900 shadow-md shadow-slate-950/30"
class:compact
class:grid-card-cover-only={coverOnly}
- on:click
+ {onclick}
>
- <slot name="overlay" />
+ {@render overlay?.()}
{#if details.cover}
<img
class="h-full w-full object-cover object-[center_top]"
@@ -76,7 +93,7 @@
</header>
<section class="max-h-full grow overflow-auto border-t border-slate-800/80 pt-2 text-xs">
- <slot />
+ {@render children?.()}
</section>
</article>
{/if}
diff --git a/frontend/src/lib/components/Cardlet.svelte b/frontend/src/lib/components/Cardlet.svelte
index 04d8599..d249cc8 100644
--- a/frontend/src/lib/components/Cardlet.svelte
+++ b/frontend/src/lib/components/Cardlet.svelte
@@ -1,14 +1,27 @@
<script lang="ts">
import type { ComicFilter } from '$gql/graphql';
import { href } from '$lib/Navigation';
+ import type { Snippet } from 'svelte';
- export let name: string;
- export let title: string | null | undefined = undefined;
+ interface Props {
+ name: string;
+ title?: string | null;
+ filter?: keyof ComicFilter;
+ id?: number | string;
+ overlay?: Snippet;
+ onclick: (event: MouseEvent) => void;
+ }
- export let filter: keyof ComicFilter | undefined = undefined;
- export let id: number | string | undefined = undefined;
+ let {
+ name,
+ title = undefined,
+ filter = undefined,
+ id = undefined,
+ overlay,
+ onclick
+ }: Props = $props();
- const handleAux = (e: MouseEvent) => {
+ const onauxclick = (e: MouseEvent) => {
if (filter === undefined || id === undefined || e.button !== 1) return;
window.open(href('comics', { filter: { include: { [filter]: { all: [id] } } } }));
};
@@ -18,10 +31,10 @@
type="button"
class="relative flex overflow-hidden rounded bg-slate-900 text-left shadow-md shadow-slate-950/20"
{title}
- on:click
- on:auxclick={handleAux}
+ {onclick}
+ {onauxclick}
>
- <slot name="overlay" />
+ {@render overlay?.()}
<article class="group h-full grow items-center gap-2 p-2 text-xs">
<h2 class="ellipsis-nowrap text-sm font-medium">{name}</h2>
</article>
diff --git a/frontend/src/lib/components/DeleteButton.svelte b/frontend/src/lib/components/DeleteButton.svelte
index 8f5f116..bc94c8c 100644
--- a/frontend/src/lib/components/DeleteButton.svelte
+++ b/frontend/src/lib/components/DeleteButton.svelte
@@ -1,15 +1,22 @@
-<script>
+<script lang="ts">
import { accelerator } from '$lib/Shortcuts';
+ import type { MouseEventHandler } from 'svelte/elements';
- export let prominent = false;
+ interface Props {
+ prominent?: boolean;
+ onclick: MouseEventHandler<HTMLButtonElement>;
+ }
+
+ let { prominent = false, onclick }: Props = $props();
</script>
<button
type="button"
class={prominent ? 'btn-rose' : 'btn-slate hover:bg-rose-700'}
title="Delete forever"
- on:click
+ aria-label="Delete forever"
+ {onclick}
use:accelerator={'Delete'}
>
- <span class="icon-base icon-[material-symbols--delete-forever]" />
+ <span class="icon-base icon-[material-symbols--delete-forever]"></span>
</button>
diff --git a/frontend/src/lib/components/Dialog.svelte b/frontend/src/lib/components/Dialog.svelte
index a0bbe5e..d300369 100644
--- a/frontend/src/lib/components/Dialog.svelte
+++ b/frontend/src/lib/components/Dialog.svelte
@@ -1,10 +1,16 @@
<script lang="ts">
import { trapFocus } from '$lib/Actions';
import { fadeDefault } from '$lib/Transitions';
- import { closeModal } from 'svelte-modals';
+ import type { Snippet } from 'svelte';
+ import type { ModalProps } from 'svelte-modals';
import { fade } from 'svelte/transition';
- export let isOpen: boolean;
+ interface Props extends ModalProps {
+ title: string;
+ children?: Snippet;
+ }
+
+ let { isOpen, close, title, children }: Props = $props();
</script>
{#if isOpen}
@@ -18,18 +24,19 @@
class="pointer-events-auto flex flex-col rounded-md bg-slate-800 shadow-md shadow-slate-900"
>
<header class="flex items-center gap-1 border-b-2 border-slate-700/50 p-2">
- <slot name="header" />
+ <h2>{title}</h2>
<button
type="button"
class="ml-auto flex items-center text-white/30 hover:text-white"
title="Cancel"
- on:click={closeModal}
+ aria-label="Cancel"
+ onclick={close}
>
- <span class="icon-base icon-[material-symbols--close]" />
+ <span class="icon-base icon-[material-symbols--close]"></span>
</button>
</header>
<main class="m-3 w-80 sm:w-[34rem]">
- <slot />
+ {@render children?.()}
</main>
</div>
</div>
diff --git a/frontend/src/lib/components/Dropdown.svelte b/frontend/src/lib/components/Dropdown.svelte
index 9e935e4..ddd20a0 100644
--- a/frontend/src/lib/components/Dropdown.svelte
+++ b/frontend/src/lib/components/Dropdown.svelte
@@ -1,18 +1,37 @@
<script lang="ts">
- import { clickOutside } from '$lib/Actions';
import { fadeFast } from '$lib/Transitions';
+ import type { Snippet } from 'svelte';
import { fade } from 'svelte/transition';
- export let visible: boolean;
- export let parent: HTMLElement;
+ interface Props {
+ button: Snippet<[() => void]>;
+ children?: Snippet;
+ }
+
+ let { button, children }: Props = $props();
+
+ let visible = $state(false);
+
+ function onfocusout(event: FocusEvent & { currentTarget: EventTarget & HTMLDivElement }) {
+ if (
+ event.relatedTarget instanceof HTMLElement &&
+ event.currentTarget.contains(event.relatedTarget)
+ ) {
+ return;
+ }
+
+ visible = false;
+ }
</script>
-{#if visible}
- <div
- class="absolute z-[1] mt-1 w-max rounded bg-slate-700 p-1 shadow-sm shadow-slate-900"
- transition:fade={fadeFast}
- use:clickOutside={{ handler: () => (visible = false), ignore: parent }}
- >
- <slot />
- </div>
-{/if}
+<div class="relative" {onfocusout}>
+ {@render button(() => (visible = !visible))}
+ {#if visible}
+ <div
+ class="absolute z-[1] mt-1 w-max rounded bg-slate-700 p-1 shadow-sm shadow-slate-900"
+ transition:fade={fadeFast}
+ >
+ {@render children?.()}
+ </div>
+ {/if}
+</div>
diff --git a/frontend/src/lib/components/Expander.svelte b/frontend/src/lib/components/Expander.svelte
index a382658..8f23042 100644
--- a/frontend/src/lib/components/Expander.svelte
+++ b/frontend/src/lib/components/Expander.svelte
@@ -1,17 +1,21 @@
<script lang="ts">
- export let expanded: boolean;
- export let title: string;
+ interface Props {
+ expanded: boolean;
+ title: string;
+ }
+
+ let { expanded = $bindable(), title }: Props = $props();
+
+ function onclick() {
+ expanded = !expanded;
+ }
</script>
-<button
- class="flex items-center text-base hover:text-white"
- type="button"
- on:click={() => (expanded = !expanded)}
->
+<button class="flex items-center text-base hover:text-white" type="button" {onclick}>
{#if expanded}
- <span class="icon-base icon-[material-symbols--expand-less]" />
+ <span class="icon-base icon-[material-symbols--expand-less]"></span>
{:else}
- <span class="icon-base icon-[material-symbols--expand-more]" />
+ <span class="icon-base icon-[material-symbols--expand-more]"></span>
{/if}
{title}
</button>
diff --git a/frontend/src/lib/components/Guard.svelte b/frontend/src/lib/components/Guard.svelte
index fd7ded4..38cbd65 100644
--- a/frontend/src/lib/components/Guard.svelte
+++ b/frontend/src/lib/components/Guard.svelte
@@ -1,9 +1,10 @@
<script lang="ts">
import { getResultState } from '$lib/Utils';
+ import type { OperationResultStore } from '@urql/svelte';
import Spinner from './Spinner.svelte';
- export let result;
- $: state = getResultState($result);
+ let { result }: { result: OperationResultStore } = $props();
+ let state = $derived(getResultState($result));
</script>
{#if state.fetching}
diff --git a/frontend/src/lib/components/Head.svelte b/frontend/src/lib/components/Head.svelte
index b4aed5b..5ddd543 100644
--- a/frontend/src/lib/components/Head.svelte
+++ b/frontend/src/lib/components/Head.svelte
@@ -1,6 +1,5 @@
<script lang="ts">
- export let section: string;
- export let title = '';
+ let { section, title = '' }: { section: string; title?: string } = $props();
function formatTitle(section: string, title?: string) {
return [title, section, 'hircine'].filter((i) => i).join(' ยท ');
diff --git a/frontend/src/lib/components/Labelled.svelte b/frontend/src/lib/components/Labelled.svelte
deleted file mode 100644
index 4b36ad6..0000000
--- a/frontend/src/lib/components/Labelled.svelte
+++ /dev/null
@@ -1,10 +0,0 @@
-<script lang="ts">
- import { idFromLabel } from '$lib/Utils';
-
- export let label: string;
-
- const id = idFromLabel(label);
-</script>
-
-<label class="self-center" for={id}>{label}</label>
-<slot {id} />
diff --git a/frontend/src/lib/components/LabelledBlock.svelte b/frontend/src/lib/components/LabelledBlock.svelte
index feb563e..8f93667 100644
--- a/frontend/src/lib/components/LabelledBlock.svelte
+++ b/frontend/src/lib/components/LabelledBlock.svelte
@@ -1,7 +1,14 @@
<script lang="ts">
import { idFromLabel } from '$lib/Utils';
+ import type { Snippet } from 'svelte';
- export let label: string;
+ interface Props {
+ label: string;
+ side?: Snippet;
+ children?: Snippet<[{ id: string }]>;
+ }
+
+ let { label, side, children }: Props = $props();
const id = idFromLabel(label);
</script>
@@ -9,10 +16,10 @@
<div class="flex flex-col">
<div class="flex">
<label for={id}>{label}</label>
- {#if $$slots.controls}
- <div class="grow" />
- <slot name="controls" />
+ {#if side}
+ <div class="grow"></div>
+ {@render side?.()}
{/if}
</div>
- <slot {id} />
+ {@render children?.({ id })}
</div>
diff --git a/frontend/src/lib/components/OrganizedButton.svelte b/frontend/src/lib/components/OrganizedButton.svelte
index 9be985c..3838f7d 100644
--- a/frontend/src/lib/components/OrganizedButton.svelte
+++ b/frontend/src/lib/components/OrganizedButton.svelte
@@ -1,9 +1,15 @@
<script lang="ts">
import Organized from '$lib/icons/Organized.svelte';
+ import type { MouseEventHandler } from 'svelte/elements';
- export let organized: boolean;
+ interface Props {
+ organized: boolean;
+ onclick: MouseEventHandler<HTMLButtonElement>;
+ }
+
+ let { organized, onclick }: Props = $props();
</script>
-<button type="button" title="Toggle organized" class="flex text-base" on:click>
+<button type="button" title="Toggle organized" class="flex text-base" {onclick}>
<Organized hoverable {organized} />
</button>
diff --git a/frontend/src/lib/components/RefreshButton.svelte b/frontend/src/lib/components/RefreshButton.svelte
index afab640..70ee2d1 100644
--- a/frontend/src/lib/components/RefreshButton.svelte
+++ b/frontend/src/lib/components/RefreshButton.svelte
@@ -1,3 +1,9 @@
-<button class="btn-blue" title="Refresh" on:click>
- <span class="icon-base icon-[material-symbols--sync]" />
+<script lang="ts">
+ import type { MouseEventHandler } from 'svelte/elements';
+
+ let { onclick }: { onclick: MouseEventHandler<HTMLButtonElement> } = $props();
+</script>
+
+<button class="btn-blue" title="Refresh" aria-label="Refresh" {onclick}>
+ <span class="icon-base icon-[material-symbols--sync]"></span>
</button>
diff --git a/frontend/src/lib/components/RemovePageButton.svelte b/frontend/src/lib/components/RemovePageButton.svelte
index e23c079..8045f32 100644
--- a/frontend/src/lib/components/RemovePageButton.svelte
+++ b/frontend/src/lib/components/RemovePageButton.svelte
@@ -1,13 +1,17 @@
<script lang="ts">
import { accelerator } from '$lib/Shortcuts';
+ import type { MouseEventHandler } from 'svelte/elements';
+
+ let { onclick }: { onclick: MouseEventHandler<HTMLButtonElement> } = $props();
</script>
<button
type="button"
class="btn-rose"
title="Remove selected pages"
- on:click
+ aria-label="Remove selected pages"
+ {onclick}
use:accelerator={'Delete'}
>
- <span class="icon-base icon-[material-symbols--scan-delete]" />
+ <span class="icon-base icon-[material-symbols--scan-delete]"></span>
</button>
diff --git a/frontend/src/lib/components/Select.svelte b/frontend/src/lib/components/Select.svelte
index dece4a5..44828d3 100644
--- a/frontend/src/lib/components/Select.svelte
+++ b/frontend/src/lib/components/Select.svelte
@@ -2,19 +2,28 @@
import type { ListItem } from '$lib/Utils';
import Svelecte from 'svelecte';
- let inputId: string;
- let valueAsObject = false;
- let multiple = false;
-
type Item = number | string | ListItem;
type Value = Item | Item[] | undefined | null;
- export let clearable = false;
- export let placeholder = 'Select...';
- export let options: ListItem[] | undefined;
- export let value: Value;
+ interface Props {
+ id: string;
+ object?: boolean;
+ multi?: boolean;
+ clearable?: boolean;
+ placeholder?: string;
+ options: ListItem[] | undefined;
+ value: Value;
+ }
- export { inputId as id, valueAsObject as object, multiple as multi };
+ let {
+ id: inputId,
+ object: valueAsObject = false,
+ multi: multiple = false,
+ clearable = false,
+ placeholder = 'Select...',
+ options,
+ value = $bindable()
+ }: Props = $props();
</script>
{#if options !== null && options !== undefined}
diff --git a/frontend/src/lib/components/Spinner.svelte b/frontend/src/lib/components/Spinner.svelte
index 946329c..1a471a7 100644
--- a/frontend/src/lib/components/Spinner.svelte
+++ b/frontend/src/lib/components/Spinner.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
import { onDestroy } from 'svelte';
- let show = false;
+ let show = $state(false);
const timeout = setTimeout(() => (show = true), 150);
onDestroy(() => clearTimeout(timeout));
@@ -9,7 +9,7 @@
{#if show}
<div class="flex h-full w-full items-center justify-center">
- <span class="spinner" />
+ <span class="spinner"></span>
</div>
{/if}
diff --git a/frontend/src/lib/components/SubmitButton.svelte b/frontend/src/lib/components/SubmitButton.svelte
index 8ac90b9..3b89ba7 100644
--- a/frontend/src/lib/components/SubmitButton.svelte
+++ b/frontend/src/lib/components/SubmitButton.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- export let active = false;
+ let { pending = false }: { pending?: boolean } = $props();
- $: title = active ? 'Save pending changes' : 'Save (no changes pending)';
+ let title = $derived(pending ? 'Save pending changes' : 'Save (no changes pending)');
</script>
-<button type="submit" class:active class="btn-slate [&.active]:btn-blue" {title}>Save</button>
+<button type="submit" class:pending class="btn-slate [&.pending]:btn-blue" {title}>Save</button>
diff --git a/frontend/src/lib/components/Titlebar.svelte b/frontend/src/lib/components/Titlebar.svelte
index 2cdfa70..fe28cfe 100644
--- a/frontend/src/lib/components/Titlebar.svelte
+++ b/frontend/src/lib/components/Titlebar.svelte
@@ -1,12 +1,15 @@
<script lang="ts">
import Star from '$lib/icons/Star.svelte';
- import { createEventDispatcher } from 'svelte';
+ import type { MouseEventHandler } from 'svelte/elements';
- export let title: string;
- export let subtitle: string | null = '';
- export let favourite: boolean | undefined = undefined;
+ interface Props {
+ title: string;
+ subtitle?: string | null;
+ favourite?: boolean;
+ onfavourite?: MouseEventHandler<HTMLButtonElement>;
+ }
- const dispatch = createEventDispatcher<{ favourite: null }>();
+ let { title, subtitle, favourite, onfavourite }: Props = $props();
</script>
<div class="flex flex-wrap gap-x-4">
@@ -16,7 +19,7 @@
type="button"
class="focus-background mr-1 flex items-center"
title="Toggle favourite"
- on:click={() => dispatch('favourite')}
+ onclick={onfavourite}
>
<Star large hoverable {favourite} />
</button>