turns out intellij formats the code before committing which causes the ci/cd checks to fail

This commit is contained in:
maxDorninger
2025-06-28 23:12:59 +02:00
parent 6bade43d38
commit 0b8b57c574
203 changed files with 4164 additions and 4174 deletions

View File

@@ -1,17 +1,17 @@
<script lang="ts">
import {Button} from '$lib/components/ui/button/index.js';
import {env} from '$env/dynamic/public';
import { Button } from '$lib/components/ui/button/index.js';
import { env } from '$env/dynamic/public';
import * as Card from '$lib/components/ui/card/index.js';
import {ImageOff} from 'lucide-svelte';
import {goto} from '$app/navigation';
import {base} from '$app/paths';
import type {MetaDataProviderSearchResult} from '$lib/types.js';
import { ImageOff } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import type { MetaDataProviderSearchResult } from '$lib/types.js';
const apiUrl = env.PUBLIC_API_URL;
let loading = $state(false);
let errorMessage = $state(null);
let {result, isShow = true}: { result: MetaDataProviderSearchResult; isShow: boolean } =
$props();
let { result, isShow = true }: { result: MetaDataProviderSearchResult; isShow: boolean } =
$props();
console.log('Add Show Card Result: ', result);
async function addMedia() {
@@ -43,27 +43,27 @@
{/if}
</Card.Title>
<Card.Description class="truncate"
>{result.overview !== '' ? result.overview : 'No overview available'}</Card.Description
>{result.overview !== '' ? result.overview : 'No overview available'}</Card.Description
>
</Card.Header>
<Card.Content class="flex flex-1 items-center justify-center">
{#if result.poster_path != null}
<img
class="h-full w-full rounded-lg object-contain"
src={result.poster_path}
alt="{result.name}'s Poster Image"
class="h-full w-full rounded-lg object-contain"
src={result.poster_path}
alt="{result.name}'s Poster Image"
/>
{:else}
<div class="flex h-full w-full items-center justify-center">
<ImageOff class="h-12 w-12 text-gray-400"/>
<ImageOff class="h-12 w-12 text-gray-400" />
</div>
{/if}
</Card.Content>
<Card.Footer class="flex flex-col items-start gap-2 rounded-b-lg border-t bg-card p-4">
<Button
class="w-full font-semibold"
disabled={result.added || loading}
onclick={() => addMedia(result)}
class="w-full font-semibold"
disabled={result.added || loading}
onclick={() => addMedia(result)}
>
{#if loading}
<span class="animate-pulse">Loading...</span>
@@ -75,9 +75,9 @@
{#if result.vote_average != null}
<span class="flex items-center text-sm font-medium text-yellow-600">
<svg class="mr-1 h-4 w-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"
><path
><path
d="M10 15l-5.878 3.09 1.122-6.545L.488 6.91l6.561-.955L10 0l2.951 5.955 6.561.955-4.756 4.635 1.122 6.545z"
/></svg
/></svg
>
Rating: {Math.round(result.vote_average)}/10
</span>

View File

@@ -1,6 +1,6 @@
<script lang="ts" module>
import {Clapperboard, Home, Info, LifeBuoy, Send, Settings, TvIcon} from 'lucide-svelte';
import {PUBLIC_VERSION} from '$env/static/public';
import { Clapperboard, Home, Info, LifeBuoy, Send, Settings, TvIcon } from 'lucide-svelte';
import { PUBLIC_VERSION } from '$env/static/public';
const data = {
navMain: [
@@ -82,11 +82,11 @@
import NavSecondary from '$lib/components/nav-secondary.svelte';
import NavUser from '$lib/components/nav-user.svelte';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import type {ComponentProps} from 'svelte';
import type { ComponentProps } from 'svelte';
import logo from '$lib/images/logo.svg';
import {base} from '$app/paths';
import { base } from '$app/paths';
let {ref = $bindable(null), ...restProps}: ComponentProps<typeof Sidebar.Root> = $props();
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
</script>
<Sidebar.Root {...restProps} bind:ref variant="inset">
@@ -94,9 +94,9 @@
<Sidebar.Menu>
<Sidebar.MenuItem>
<Sidebar.MenuButton size="lg">
{#snippet child({props})}
{#snippet child({ props })}
<a href="{base}/dashboard" {...props}>
<img class="size-12" src={logo} alt="Media Manager Logo"/>
<img class="size-12" src={logo} alt="Media Manager Logo" />
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Media Manager</span>
<span class="truncate text-xs">v{PUBLIC_VERSION}</span>
@@ -108,11 +108,11 @@
</Sidebar.Menu>
</Sidebar.Header>
<Sidebar.Content>
<NavMain items={data.navMain}/>
<NavMain items={data.navMain} />
<!-- <NavProjects projects={data.projects}/> -->
<NavSecondary class="mt-auto" items={data.navSecondary}/>
<NavSecondary class="mt-auto" items={data.navSecondary} />
</Sidebar.Content>
<Sidebar.Footer>
<NavUser/>
<NavUser />
</Sidebar.Footer>
</Sidebar.Root>

View File

@@ -1,12 +1,12 @@
<script>
import X from '@lucide/svelte/icons/x';
import Check from '@lucide/svelte/icons/check';
import X from '@lucide/svelte/icons/x';
import Check from '@lucide/svelte/icons/check';
let {state} = $props();
let { state } = $props();
</script>
{#if state}
<Check class="stroke-green-500"/>
<Check class="stroke-green-500" />
{:else}
<X class="stroke-rose-600"/>
<X class="stroke-rose-600" />
{/if}

View File

@@ -1,67 +1,67 @@
<script lang="ts">
import {cn} from '$lib/utils'; // Assuming you have the cn utility from shadcn-svelte
import { cn } from '$lib/utils'; // Assuming you have the cn utility from shadcn-svelte
let {
label,
variant = 'default',
size = 'default',
onClose = undefined,
class: className = ''
} = $props<{
label: string;
variant?: 'default' | 'secondary' | 'outline' | 'destructive';
size?: 'default' | 'sm' | 'lg';
onClose?: () => void;
class?: string;
}>();
let {
label,
variant = 'default',
size = 'default',
onClose = undefined,
class: className = ''
} = $props<{
label: string;
variant?: 'default' | 'secondary' | 'outline' | 'destructive';
size?: 'default' | 'sm' | 'lg';
onClose?: () => void;
class?: string;
}>();
// Base styles for the chip
const baseStyles =
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50';
// Base styles for the chip
const baseStyles =
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50';
// Variant styles
const variantStyles = {
default:
'border bg-background text-foreground shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90'
};
// Variant styles
const variantStyles = {
default:
'border bg-background text-foreground shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90'
};
// Size styles
const sizeStyles = {
default: 'h-9 px-3 py-0.5', // Adjusted height for New York style
sm: 'h-7 px-2 py-0.5 text-xs', // Adjusted height for New York style
lg: 'h-10 px-4 py-0.5' // Adjusted height for New York style
};
// Size styles
const sizeStyles = {
default: 'h-9 px-3 py-0.5', // Adjusted height for New York style
sm: 'h-7 px-2 py-0.5 text-xs', // Adjusted height for New York style
lg: 'h-10 px-4 py-0.5' // Adjusted height for New York style
};
// Styles for the close button
const closeButtonStyles =
'ml-1 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full';
// Styles for the close button
const closeButtonStyles =
'ml-1 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full';
</script>
<div class={cn(baseStyles, variantStyles[variant], sizeStyles[size], className)}>
{label}
{#if onClose}
<button
class={cn(closeButtonStyles, 'hover:bg-accent-foreground/20')}
onclick={onClose}
aria-label="remove tag"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3"
>
<path d="M18 6L6 18"/>
<path d="M6 6L18 18"/>
</svg>
</button>
{/if}
{label}
{#if onClose}
<button
class={cn(closeButtonStyles, 'hover:bg-accent-foreground/20')}
onclick={onClose}
aria-label="remove tag"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3"
>
<path d="M18 6L6 18" />
<path d="M6 6L18 18" />
</svg>
</button>
{/if}
</div>

View File

@@ -1,177 +1,177 @@
<script lang="ts">
import {env} from '$env/dynamic/public';
import {Button, buttonVariants} from '$lib/components/ui/button/index.js';
import {Input} from '$lib/components/ui/input';
import {Label} from '$lib/components/ui/label';
import {toast} from 'svelte-sonner';
import {Badge} from '$lib/components/ui/badge/index.js';
import { env } from '$env/dynamic/public';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { toast } from 'svelte-sonner';
import { Badge } from '$lib/components/ui/badge/index.js';
import type {PublicIndexerQueryResult} from '$lib/types.js';
import {getFullyQualifiedMediaName} from '$lib/utils';
import {LoaderCircle} from 'lucide-svelte';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import * as Tabs from '$lib/components/ui/tabs/index.js';
import * as Select from '$lib/components/ui/select/index.js';
import * as Table from '$lib/components/ui/table/index.js';
import type { PublicIndexerQueryResult } from '$lib/types.js';
import { getFullyQualifiedMediaName } from '$lib/utils';
import { LoaderCircle } from 'lucide-svelte';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import * as Tabs from '$lib/components/ui/tabs/index.js';
import * as Select from '$lib/components/ui/select/index.js';
import * as Table from '$lib/components/ui/table/index.js';
const apiUrl = env.PUBLIC_API_URL;
let {movie} = $props();
let dialogueState = $state(false);
let torrents: PublicIndexerQueryResult[] = $state([]);
let isLoadingTorrents: boolean = $state(false);
let torrentsError: string | null = $state(null);
let queryOverride: string = $state('');
let filePathSuffix: string = $state('');
const apiUrl = env.PUBLIC_API_URL;
let { movie } = $props();
let dialogueState = $state(false);
let torrents: PublicIndexerQueryResult[] = $state([]);
let isLoadingTorrents: boolean = $state(false);
let torrentsError: string | null = $state(null);
let queryOverride: string = $state('');
let filePathSuffix: string = $state('');
async function downloadTorrent(result_id: string) {
let url = new URL(apiUrl + `/movies/${movie.id}/torrents`);
url.searchParams.append('public_indexer_result_id', result_id);
if (filePathSuffix !== '') {
url.searchParams.append('file_path_suffix', filePathSuffix);
}
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
async function downloadTorrent(result_id: string) {
let url = new URL(apiUrl + `/movies/${movie.id}/torrents`);
url.searchParams.append('public_indexer_result_id', result_id);
if (filePathSuffix !== '') {
url.searchParams.append('file_path_suffix', filePathSuffix);
}
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
if (!response.ok) {
const errorMessage = `Failed to download torrent for movie ${movie.id}: ${response.statusText}`;
console.error(errorMessage);
torrentsError = errorMessage;
toast.error(errorMessage);
return false;
}
if (!response.ok) {
const errorMessage = `Failed to download torrent for movie ${movie.id}: ${response.statusText}`;
console.error(errorMessage);
torrentsError = errorMessage;
toast.error(errorMessage);
return false;
}
const data: PublicIndexerQueryResult[] = await response.json();
console.log('Downloading torrent:', data);
toast.success('Torrent download started successfully!');
const data: PublicIndexerQueryResult[] = await response.json();
console.log('Downloading torrent:', data);
toast.success('Torrent download started successfully!');
return true;
} catch (err) {
const errorMessage = `Error downloading torrent: ${err instanceof Error ? err.message : 'An unknown error occurred'}`;
console.error(errorMessage);
toast.error(errorMessage);
return false;
}
}
return true;
} catch (err) {
const errorMessage = `Error downloading torrent: ${err instanceof Error ? err.message : 'An unknown error occurred'}`;
console.error(errorMessage);
toast.error(errorMessage);
return false;
}
}
async function getTorrents(override: boolean = false): Promise<PublicIndexerQueryResult[]> {
isLoadingTorrents = true;
torrentsError = null;
torrents = [];
async function getTorrents(override: boolean = false): Promise<PublicIndexerQueryResult[]> {
isLoadingTorrents = true;
torrentsError = null;
torrents = [];
let url = new URL(apiUrl + `/movies/${movie.id}/torrents`);
if (override) {
url.searchParams.append('search_query_override', queryOverride);
}
let url = new URL(apiUrl + `/movies/${movie.id}/torrents`);
if (override) {
url.searchParams.append('search_query_override', queryOverride);
}
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
if (!response.ok) {
const errorMessage = `Failed to fetch torrents for movie ${movie.id}: ${response.statusText}`;
console.error(errorMessage);
torrentsError = errorMessage;
if (dialogueState) toast.error(errorMessage);
return [];
}
if (!response.ok) {
const errorMessage = `Failed to fetch torrents for movie ${movie.id}: ${response.statusText}`;
console.error(errorMessage);
torrentsError = errorMessage;
if (dialogueState) toast.error(errorMessage);
return [];
}
const data: PublicIndexerQueryResult[] = await response.json();
console.log('Fetched torrents:', data);
if (dialogueState) {
if (data.length > 0) {
toast.success(`Found ${data.length} torrents.`);
} else {
toast.info('No torrents found for your query.');
}
}
return data;
} catch (err) {
const errorMessage = `Error fetching torrents: ${err instanceof Error ? err.message : 'An unknown error occurred'}`;
console.error(errorMessage);
torrentsError = errorMessage;
if (dialogueState) toast.error(errorMessage);
return [];
} finally {
isLoadingTorrents = false;
}
}
const data: PublicIndexerQueryResult[] = await response.json();
console.log('Fetched torrents:', data);
if (dialogueState) {
if (data.length > 0) {
toast.success(`Found ${data.length} torrents.`);
} else {
toast.info('No torrents found for your query.');
}
}
return data;
} catch (err) {
const errorMessage = `Error fetching torrents: ${err instanceof Error ? err.message : 'An unknown error occurred'}`;
console.error(errorMessage);
torrentsError = errorMessage;
if (dialogueState) toast.error(errorMessage);
return [];
} finally {
isLoadingTorrents = false;
}
}
$effect(() => {
if (movie?.id) {
getTorrents().then((fetchedTorrents) => {
if (!isLoadingTorrents) {
torrents = fetchedTorrents;
} else if (fetchedTorrents.length > 0 || torrentsError) {
torrents = fetchedTorrents;
}
});
}
});
$effect(() => {
if (movie?.id) {
getTorrents().then((fetchedTorrents) => {
if (!isLoadingTorrents) {
torrents = fetchedTorrents;
} else if (fetchedTorrents.length > 0 || torrentsError) {
torrents = fetchedTorrents;
}
});
}
});
</script>
{#snippet saveDirectoryPreview(movie, filePathSuffix)}
/{getFullyQualifiedMediaName(movie)} [{movie.metadata_provider}id-{movie.external_id}
]/{movie.name}{filePathSuffix === '' ? '' : ' - ' + filePathSuffix}.mkv
/{getFullyQualifiedMediaName(movie)} [{movie.metadata_provider}id-{movie.external_id}
]/{movie.name}{filePathSuffix === '' ? '' : ' - ' + filePathSuffix}.mkv
{/snippet}
<Dialog.Root bind:open={dialogueState}>
<Dialog.Trigger class={buttonVariants({ variant: 'default' })}>Download Movie</Dialog.Trigger>
<Dialog.Content class="max-h-[90vh] w-fit min-w-[80vw] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Download a Movie</Dialog.Title>
<Dialog.Description>
Search and download torrents for a specific season or season packs.
</Dialog.Description>
</Dialog.Header>
<Tabs.Root class="w-full" value="basic">
<Tabs.List>
<Tabs.Trigger value="basic">Standard Mode</Tabs.Trigger>
<Tabs.Trigger value="advanced">Advanced Mode</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="basic">
<div class="grid w-full items-center gap-1.5">
<Label for="file-suffix">Filepath suffix</Label>
<Select.Root bind:value={filePathSuffix} id="file-suffix" type="single">
<Select.Trigger class="w-[180px]">{filePathSuffix}</Select.Trigger>
<Select.Content>
<Select.Item value="">None</Select.Item>
<Select.Item value="2160P">2160p</Select.Item>
<Select.Item value="1080P">1080p</Select.Item>
<Select.Item value="720P">720p</Select.Item>
<Select.Item value="480P">480p</Select.Item>
<Select.Item value="360P">360p</Select.Item>
</Select.Content>
</Select.Root>
<p class="text-sm text-muted-foreground">
This is necessary to differentiate between versions of the same movie, for example a
1080p and a 4K version.
</p>
<Label for="file-suffix-display"
>The files will be saved in the following directory:</Label
>
<p class="text-sm text-muted-foreground" id="file-suffix-display">
{@render saveDirectoryPreview(movie, filePathSuffix)}
</p>
</div>
</Tabs.Content>
<Tabs.Content value="advanced">
<div class="grid w-full items-center gap-1.5">
<Label for="query-override">Enter a custom query</Label>
<div class="flex w-full max-w-sm items-center space-x-2">
<Input bind:value={queryOverride} id="query-override" type="text"/>
<Button
onclick={async () => {
<Dialog.Trigger class={buttonVariants({ variant: 'default' })}>Download Movie</Dialog.Trigger>
<Dialog.Content class="max-h-[90vh] w-fit min-w-[80vw] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Download a Movie</Dialog.Title>
<Dialog.Description>
Search and download torrents for a specific season or season packs.
</Dialog.Description>
</Dialog.Header>
<Tabs.Root class="w-full" value="basic">
<Tabs.List>
<Tabs.Trigger value="basic">Standard Mode</Tabs.Trigger>
<Tabs.Trigger value="advanced">Advanced Mode</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="basic">
<div class="grid w-full items-center gap-1.5">
<Label for="file-suffix">Filepath suffix</Label>
<Select.Root bind:value={filePathSuffix} id="file-suffix" type="single">
<Select.Trigger class="w-[180px]">{filePathSuffix}</Select.Trigger>
<Select.Content>
<Select.Item value="">None</Select.Item>
<Select.Item value="2160P">2160p</Select.Item>
<Select.Item value="1080P">1080p</Select.Item>
<Select.Item value="720P">720p</Select.Item>
<Select.Item value="480P">480p</Select.Item>
<Select.Item value="360P">360p</Select.Item>
</Select.Content>
</Select.Root>
<p class="text-sm text-muted-foreground">
This is necessary to differentiate between versions of the same movie, for example a
1080p and a 4K version.
</p>
<Label for="file-suffix-display"
>The files will be saved in the following directory:</Label
>
<p class="text-sm text-muted-foreground" id="file-suffix-display">
{@render saveDirectoryPreview(movie, filePathSuffix)}
</p>
</div>
</Tabs.Content>
<Tabs.Content value="advanced">
<div class="grid w-full items-center gap-1.5">
<Label for="query-override">Enter a custom query</Label>
<div class="flex w-full max-w-sm items-center space-x-2">
<Input bind:value={queryOverride} id="query-override" type="text" />
<Button
onclick={async () => {
isLoadingTorrents = true;
torrentsError = null;
torrents = [];
@@ -183,86 +183,86 @@
isLoadingTorrents = false;
}
}}
variant="secondary"
>
Search
</Button>
</div>
<p class="text-sm text-muted-foreground">
The custom query will override the default search string like "A Minecraft Movie
(2025)".
</p>
<Label for="file-suffix">Filepath suffix</Label>
<Input
bind:value={filePathSuffix}
class="max-w-sm"
id="file-suffix"
placeholder="1080P"
type="text"
/>
<p class="text-sm text-muted-foreground">
This is necessary to differentiate between versions of the same movie, for example a
1080p and a 4K version.
</p>
variant="secondary"
>
Search
</Button>
</div>
<p class="text-sm text-muted-foreground">
The custom query will override the default search string like "A Minecraft Movie
(2025)".
</p>
<Label for="file-suffix">Filepath suffix</Label>
<Input
bind:value={filePathSuffix}
class="max-w-sm"
id="file-suffix"
placeholder="1080P"
type="text"
/>
<p class="text-sm text-muted-foreground">
This is necessary to differentiate between versions of the same movie, for example a
1080p and a 4K version.
</p>
<Label for="file-suffix-display"
>The files will be saved in the following directory:</Label
>
<p class="text-sm text-muted-foreground" id="file-suffix-display">
{@render saveDirectoryPreview(movie, filePathSuffix)}
</p>
</div>
</Tabs.Content>
</Tabs.Root>
<div class="mt-4 items-center">
{#if isLoadingTorrents}
<div class="flex w-full max-w-sm items-center space-x-2">
<LoaderCircle class="animate-spin"/>
<p>Loading torrents...</p>
</div>
{:else if torrentsError}
<p class="text-red-500">Error: {torrentsError}</p>
{:else if torrents.length > 0}
<h3 class="mb-2 text-lg font-semibold">Found Torrents:</h3>
<div class="max-h-[200px] overflow-y-auto rounded-md border p-2">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Title</Table.Head>
<Table.Head>Size</Table.Head>
<Table.Head>Seeders</Table.Head>
<Table.Head>Indexer Flags</Table.Head>
<Table.Head class="text-right">Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each torrents as torrent (torrent.id)}
<Table.Row>
<Table.Cell class="max-w-[300px] font-medium">{torrent.title}</Table.Cell>
<Table.Cell>{(torrent.size / 1024 / 1024 / 1024).toFixed(2)}GB</Table.Cell>
<Table.Cell>{torrent.seeders}</Table.Cell>
<Table.Cell>
{#each torrent.flags as flag}
<Badge variant="outline">{flag}</Badge>
{/each}
</Table.Cell>
<Table.Cell class="text-right">
<Button
size="sm"
variant="outline"
onclick={() => {
<Label for="file-suffix-display"
>The files will be saved in the following directory:</Label
>
<p class="text-sm text-muted-foreground" id="file-suffix-display">
{@render saveDirectoryPreview(movie, filePathSuffix)}
</p>
</div>
</Tabs.Content>
</Tabs.Root>
<div class="mt-4 items-center">
{#if isLoadingTorrents}
<div class="flex w-full max-w-sm items-center space-x-2">
<LoaderCircle class="animate-spin" />
<p>Loading torrents...</p>
</div>
{:else if torrentsError}
<p class="text-red-500">Error: {torrentsError}</p>
{:else if torrents.length > 0}
<h3 class="mb-2 text-lg font-semibold">Found Torrents:</h3>
<div class="max-h-[200px] overflow-y-auto rounded-md border p-2">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Title</Table.Head>
<Table.Head>Size</Table.Head>
<Table.Head>Seeders</Table.Head>
<Table.Head>Indexer Flags</Table.Head>
<Table.Head class="text-right">Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each torrents as torrent (torrent.id)}
<Table.Row>
<Table.Cell class="max-w-[300px] font-medium">{torrent.title}</Table.Cell>
<Table.Cell>{(torrent.size / 1024 / 1024 / 1024).toFixed(2)}GB</Table.Cell>
<Table.Cell>{torrent.seeders}</Table.Cell>
<Table.Cell>
{#each torrent.flags as flag}
<Badge variant="outline">{flag}</Badge>
{/each}
</Table.Cell>
<Table.Cell class="text-right">
<Button
size="sm"
variant="outline"
onclick={() => {
downloadTorrent(torrent.id);
}}
>
Download
</Button>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
{/if}
</div>
</Dialog.Content>
>
Download
</Button>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
{/if}
</div>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,21 +1,21 @@
<script lang="ts">
import {env} from '$env/dynamic/public';
import {Button, buttonVariants} from '$lib/components/ui/button/index.js';
import {Input} from '$lib/components/ui/input';
import {Label} from '$lib/components/ui/label';
import {toast} from 'svelte-sonner';
import { env } from '$env/dynamic/public';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { toast } from 'svelte-sonner';
import type {PublicIndexerQueryResult} from '$lib/types.js';
import {convertTorrentSeasonRangeToIntegerRange, getFullyQualifiedMediaName} from '$lib/utils';
import {LoaderCircle} from 'lucide-svelte';
import type { PublicIndexerQueryResult } from '$lib/types.js';
import { convertTorrentSeasonRangeToIntegerRange, getFullyQualifiedMediaName } from '$lib/utils';
import { LoaderCircle } from 'lucide-svelte';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import * as Tabs from '$lib/components/ui/tabs/index.js';
import * as Select from '$lib/components/ui/select/index.js';
import * as Table from '$lib/components/ui/table/index.js';
import {Badge} from '$lib/components/ui/badge';
import { Badge } from '$lib/components/ui/badge';
const apiUrl = env.PUBLIC_API_URL;
let {show} = $props();
let { show } = $props();
let dialogueState = $state(false);
let selectedSeasonNumber: number = $state(1);
let torrents: PublicIndexerQueryResult[] = $state([]);
@@ -62,8 +62,8 @@
}
async function getTorrents(
season_number: number,
override: boolean = false
season_number: number,
override: boolean = false
): Promise<PublicIndexerQueryResult[]> {
isLoadingTorrents = true;
torrentsError = null;
@@ -152,14 +152,14 @@
<div class="grid w-full items-center gap-1.5">
{#if show?.seasons?.length > 0}
<Label for="season-number"
>Enter a season number from 1 to {show.seasons.at(-1).number}</Label
>Enter a season number from 1 to {show.seasons.at(-1).number}</Label
>
<Input
type="number"
class="max-w-sm"
id="season-number"
bind:value={selectedSeasonNumber}
max={show.seasons.at(-1).number}
type="number"
class="max-w-sm"
id="season-number"
bind:value={selectedSeasonNumber}
max={show.seasons.at(-1).number}
/>
<p class="text-sm text-muted-foreground">
Enter the season's number you want to search for. The first, usually 1, or the last
@@ -183,7 +183,7 @@
example a 1080p and a 4K version of a season.
</p>
<Label for="file-suffix-display"
>The files will be saved in the following directory:</Label
>The files will be saved in the following directory:</Label
>
<p class="text-sm text-muted-foreground" id="file-suffix-display">
{@render saveDirectoryPreview(show, filePathSuffix)}
@@ -200,10 +200,10 @@
{#if show?.seasons?.length > 0}
<Label for="query-override">Enter a custom query</Label>
<div class="flex w-full max-w-sm items-center space-x-2">
<Input type="text" id="query-override" bind:value={queryOverride}/>
<Input type="text" id="query-override" bind:value={queryOverride} />
<Button
variant="secondary"
onclick={async () => {
variant="secondary"
onclick={async () => {
isLoadingTorrents = true;
torrentsError = null;
torrents = [];
@@ -225,11 +225,11 @@
</p>
<Label for="file-suffix">Filepath suffix</Label>
<Input
type="text"
class="max-w-sm"
id="file-suffix"
bind:value={filePathSuffix}
placeholder="1080P"
type="text"
class="max-w-sm"
id="file-suffix"
bind:value={filePathSuffix}
placeholder="1080P"
/>
<p class="text-sm text-muted-foreground">
This is necessary to differentiate between versions of the same season/show, for
@@ -237,7 +237,7 @@
</p>
<Label for="file-suffix-display"
>The files will be saved in the following directory:</Label
>The files will be saved in the following directory:</Label
>
<p class="text-sm text-muted-foreground" id="file-suffix-display">
{@render saveDirectoryPreview(show, filePathSuffix)}
@@ -253,7 +253,7 @@
<div class="mt-4 items-center">
{#if isLoadingTorrents}
<div class="flex w-full max-w-sm items-center space-x-2">
<LoaderCircle class="animate-spin"/>
<LoaderCircle class="animate-spin" />
<p>Loading torrents...</p>
</div>
{:else if torrentsError}
@@ -289,9 +289,9 @@
</Table.Cell>
<Table.Cell class="text-right">
<Button
size="sm"
variant="outline"
onclick={() => {
size="sm"
variant="outline"
onclick={() => {
downloadTorrent(torrent.id);
}}
>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {Progress} from '$lib/components/ui/progress/index.js';
import {onMount} from 'svelte';
import { Progress } from '$lib/components/ui/progress/index.js';
import { onMount } from 'svelte';
let value = $state(0);
let value = $state(0);
onMount(() => {
const interval = setInterval(() => {
value += 1;
}, 1);
onMount(() => {
const interval = setInterval(() => {
value += 1;
}, 1);
return () => clearInterval(interval);
});
return () => clearInterval(interval);
});
</script>
<Progress {value}/>
<Progress {value} />

View File

@@ -1,17 +1,17 @@
<script lang="ts">
import {Button} from '$lib/components/ui/button/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import {Input} from '$lib/components/ui/input/index.js';
import {Label} from '$lib/components/ui/label/index.js';
import {goto} from '$app/navigation';
import {env} from '$env/dynamic/public';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { goto } from '$app/navigation';
import { env } from '$env/dynamic/public';
import * as Tabs from '$lib/components/ui/tabs/index.js';
import {toast} from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import LoadingBar from '$lib/components/loading-bar.svelte';
const apiUrl = env.PUBLIC_API_URL;
let {oauthProvider} = $props();
let { oauthProvider } = $props();
let oauthProviderName = $derived(oauthProvider.oauth_name);
let email = $state('');
@@ -113,21 +113,21 @@
async function handleOauth() {
try {
const response = await fetch(
apiUrl + '/auth/cookie/' + oauthProviderName + '/authorize?scopes=email',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
apiUrl + '/auth/cookie/' + oauthProviderName + '/authorize?scopes=email',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
);
if (response.ok) {
let result = await response.json();
console.log(
'Redirecting to OAuth provider:',
oauthProviderName,
'url: ',
result.authorization_url
'Redirecting to OAuth provider:',
oauthProviderName,
'url: ',
result.authorization_url
);
toast.success('Redirecting to ' + oauthProviderName + ' for authentication...');
window.location = result.authorization_url;
@@ -146,18 +146,18 @@
{#snippet oauthLogin()}
{#await oauthProvider}
<LoadingBar/>
<LoadingBar />
{:then result}
{#if result.oauth_name != null}
<div
class="relative mt-2 text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
class="relative mt-2 text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
<span class="relative z-10 bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
<Button class="mt-2 w-full" onclick={() => handleOauth()} variant="outline"
>Login with {result.oauth_name}</Button
>Login with {result.oauth_name}</Button
>
{/if}
{/await}
@@ -174,11 +174,11 @@
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
bind:value={email}
id="email"
placeholder="m@example.com"
required
type="email"
bind:value={email}
id="email"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div class="grid gap-2">
@@ -189,7 +189,7 @@
Forgot your password?
</a>
</div>
<Input bind:value={password} id="password" required type="password"/>
<Input bind:value={password} id="password" required type="password" />
</div>
{#if errorMessage}
@@ -226,18 +226,18 @@
<div class="grid gap-2">
<Label for="email2">Email</Label>
<Input
bind:value={email}
id="email2"
placeholder="m@example.com"
required
type="email"
bind:value={email}
id="email2"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password2">Password</Label>
</div>
<Input bind:value={password} id="password2" required type="password"/>
<Input bind:value={password} id="password2" required type="password" />
</div>
{#if errorMessage}
@@ -256,7 +256,7 @@
<div class="mt-4 text-center text-sm">
<Button onclick={() => (tabValue = 'login')} variant="link"
>Already have an account? Login
>Already have an account? Login
</Button>
</div>
</Card.Content>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import logo from '$lib/images/logo.svg';
import logo from '$lib/images/logo.svg';
let props = $props();
let props = $props();
</script>
<div {...props} class="flex items-center">
<img alt="Logo" class="mr-2 h-12 w-12" src={logo}/>
<span class="text-3xl font-bold dark:text-white">Media Manager</span>
<img alt="Logo" class="mr-2 h-12 w-12" src={logo} />
<span class="text-3xl font-bold dark:text-white">Media Manager</span>
</div>

View File

@@ -1,18 +1,18 @@
<script>
import {getFullyQualifiedMediaName} from '$lib/utils.js';
import {env} from '$env/dynamic/public';
import { getFullyQualifiedMediaName } from '$lib/utils.js';
import { env } from '$env/dynamic/public';
const apiUrl = env.PUBLIC_API_URL;
let {media} = $props();
console.log('got media: ', media);
const apiUrl = env.PUBLIC_API_URL;
let { media } = $props();
console.log('got media: ', media);
</script>
<picture>
<source srcset="{apiUrl}/static/image/{media.id}.avif" type="image/avif"/>
<source srcset="{apiUrl}/static/image/{media.id}.webp" type="image/webp"/>
<img
alt="{getFullyQualifiedMediaName(media)}'s Poster Image"
class="aspect-9/16 center h-auto w-full rounded-lg object-cover"
src="{apiUrl}/static/image/{media.id}.jpeg"
/>
<source srcset="{apiUrl}/static/image/{media.id}.avif" type="image/avif" />
<source srcset="{apiUrl}/static/image/{media.id}.webp" type="image/webp" />
<img
alt="{getFullyQualifiedMediaName(media)}'s Poster Image"
class="aspect-9/16 center h-auto w-full rounded-lg object-cover"
src="{apiUrl}/static/image/{media.id}.jpeg"
/>
</picture>

View File

@@ -26,24 +26,24 @@
<Sidebar.Menu>
{#each items as mainItem (mainItem.title)}
<Collapsible.Root open={mainItem.isActive}>
{#snippet child({props})}
{#snippet child({ props })}
<Sidebar.MenuItem {...props}>
<Sidebar.MenuButton>
{#snippet tooltipContent()}
{mainItem.title}
{/snippet}
{#snippet child({props})}
{#snippet child({ props })}
<a href={mainItem.url} {...props}>
<mainItem.icon/>
<mainItem.icon />
<span>{mainItem.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
{#if mainItem.items?.length}
<Collapsible.Trigger>
{#snippet child({props})}
{#snippet child({ props })}
<Sidebar.MenuAction {...props} class="data-[state=open]:rotate-90">
<ChevronRight/>
<ChevronRight />
<span class="sr-only">Toggle</span>
</Sidebar.MenuAction>
{/snippet}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import {useSidebar} from '$lib/components/ui/sidebar/index.js';
import { useSidebar } from '$lib/components/ui/sidebar/index.js';
import Ellipsis from '@lucide/svelte/icons/ellipsis';
import Folder from '@lucide/svelte/icons/folder';
import Share from '@lucide/svelte/icons/share';
@@ -28,38 +28,38 @@
{#each projects as item (item.name)}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({props})}
{#snippet child({ props })}
<a href={item.url} {...props}>
<item.icon/>
<item.icon />
<span>{item.name}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({props})}
{#snippet child({ props })}
<Sidebar.MenuAction showOnHover {...props}>
<Ellipsis/>
<Ellipsis />
<span class="sr-only">More</span>
</Sidebar.MenuAction>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-48"
side={sidebar.isMobile ? 'bottom' : 'right'}
align={sidebar.isMobile ? 'end' : 'start'}
class="w-48"
side={sidebar.isMobile ? 'bottom' : 'right'}
align={sidebar.isMobile ? 'end' : 'start'}
>
<DropdownMenu.Item>
<Folder class="text-muted-foreground"/>
<Folder class="text-muted-foreground" />
<span>View Project</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<Share class="text-muted-foreground"/>
<Share class="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenu.Item>
<DropdownMenu.Separator/>
<DropdownMenu.Separator />
<DropdownMenu.Item>
<Trash2 class="text-muted-foreground"/>
<Trash2 class="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenu.Item>
</DropdownMenu.Content>

View File

@@ -1,54 +1,54 @@
<script lang="ts">
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import type {ComponentProps} from 'svelte';
import Sun from '@lucide/svelte/icons/sun';
import Moon from '@lucide/svelte/icons/moon';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import type { ComponentProps } from 'svelte';
import Sun from '@lucide/svelte/icons/sun';
import Moon from '@lucide/svelte/icons/moon';
import {toggleMode} from 'mode-watcher';
import { toggleMode } from 'mode-watcher';
let {
ref = $bindable(null),
items,
...restProps
}: {
items: {
title: string;
url: string;
// This should be `Component` after @lucide/svelte updates types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
icon: any;
}[];
} & ComponentProps<typeof Sidebar.Group> = $props();
let {
ref = $bindable(null),
items,
...restProps
}: {
items: {
title: string;
url: string;
// This should be `Component` after @lucide/svelte updates types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
icon: any;
}[];
} & ComponentProps<typeof Sidebar.Group> = $props();
</script>
<Sidebar.Group {...restProps} bind:ref>
<Sidebar.GroupContent>
<Sidebar.Menu>
<Sidebar.MenuItem>
<Sidebar.MenuButton size="sm">
{#snippet child({props})}
<div onclick={() => toggleMode()} {...props}>
<Sun class="dark:hidden "/>
<span class="dark:hidden">Switch to dark mode</span>
<Moon class="hidden dark:inline"/>
<span class="hidden dark:inline">Switch to light mode</span>
</div>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
<Sidebar.GroupContent>
<Sidebar.Menu>
<Sidebar.MenuItem>
<Sidebar.MenuButton size="sm">
{#snippet child({ props })}
<div onclick={() => toggleMode()} {...props}>
<Sun class="dark:hidden " />
<span class="dark:hidden">Switch to dark mode</span>
<Moon class="hidden dark:inline" />
<span class="hidden dark:inline">Switch to light mode</span>
</div>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{#each items as item (item.title)}
<Sidebar.MenuItem>
<Sidebar.MenuButton size="sm">
{#snippet child({props})}
<a href={item.url} {...props}>
<item.icon/>
<span>{item.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
{#each items as item (item.title)}
<Sidebar.MenuItem>
<Sidebar.MenuButton size="sm">
{#snippet child({ props })}
<a href={item.url} {...props}>
<item.icon />
<span>{item.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>

View File

@@ -5,12 +5,12 @@
import * as Avatar from '$lib/components/ui/avatar/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import {useSidebar} from '$lib/components/ui/sidebar/index.js';
import { useSidebar } from '$lib/components/ui/sidebar/index.js';
import UserDetails from './user-details.svelte';
import UserRound from '@lucide/svelte/icons/user-round';
import {handleLogout} from '$lib/utils.ts';
import {goto} from '$app/navigation';
import {base} from '$app/paths';
import { handleLogout } from '$lib/utils.ts';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
const sidebar = useSidebar();
</script>
@@ -18,51 +18,51 @@
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({props})}
{#snippet child({ props })}
<Sidebar.MenuButton
{...props}
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
{...props}
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar.Root class="h-8 w-8 rounded-lg">
<!--<Avatar.Image src={user.avatar} alt={user.name} />-->
<Avatar.Fallback class="rounded-lg">
<UserRound/>
<UserRound />
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<UserDetails/>
<UserDetails />
</div>
<ChevronsUpDown class="ml-auto size-4"/>
<ChevronsUpDown class="ml-auto size-4" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
align="end"
class="w-[var(--bits-dropdown-menu-anchor-width)] min-w-56 rounded-lg"
side={sidebar.isMobile ? 'bottom' : 'right'}
sideOffset={4}
align="end"
class="w-[var(--bits-dropdown-menu-anchor-width)] min-w-56 rounded-lg"
side={sidebar.isMobile ? 'bottom' : 'right'}
sideOffset={4}
>
<DropdownMenu.Label class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar.Root class="h-8 w-8 rounded-lg">
<!--<Avatar.Image src={user.avatar} alt={user.name} />-->
<Avatar.Fallback class="rounded-lg">
<UserRound/>
<UserRound />
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<UserDetails/>
<UserDetails />
</div>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator/>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={() => goto(base + '/dashboard/settings#me')}>
My Account
</DropdownMenu.Item>
<DropdownMenu.Separator/>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={() => handleLogout()}>
<LogOut/>
<LogOut />
Log out
</DropdownMenu.Item>
</DropdownMenu.Content>

View File

@@ -1,43 +1,43 @@
<script lang="ts">
import type {MetaDataProviderSearchResult} from '$lib/types';
import AddMediaCard from '$lib/components/add-media-card.svelte';
import {Skeleton} from '$lib/components/ui/skeleton';
import {Button} from '$lib/components/ui/button';
import {ChevronRight} from 'lucide-svelte';
import type { MetaDataProviderSearchResult } from '$lib/types';
import AddMediaCard from '$lib/components/add-media-card.svelte';
import { Skeleton } from '$lib/components/ui/skeleton';
import { Button } from '$lib/components/ui/button';
import { ChevronRight } from 'lucide-svelte';
let {
media,
isShow,
isLoading
}: {
media: MetaDataProviderSearchResult[];
isShow: boolean;
isLoading: boolean;
} = $props();
let {
media,
isShow,
isLoading
}: {
media: MetaDataProviderSearchResult[];
isShow: boolean;
isLoading: boolean;
} = $props();
</script>
<div
class="grid w-full gap-4 sm:grid-cols-1
class="grid w-full gap-4 sm:grid-cols-1
md:grid-cols-2 lg:grid-cols-3"
>
{#if isLoading}
<Skeleton class="h-[70vh] w-full"/>
<Skeleton class="h-[70vh] w-full"/>
<Skeleton class="h-[70vh] w-full"/>
{:else}
{#each media.slice(0, 3) as mediaItem}
<AddMediaCard {isShow} result={mediaItem}/>
{/each}
{/if}
{#if isShow}
<Button class="md:col-start-2" variant="secondary" href="/dashboard/tv/add-show">
More recommendations
<ChevronRight/>
</Button>
{:else}
<Button class="md:col-start-2" variant="secondary" href="/dashboard/movies/add-movie">
More recommendations
<ChevronRight/>
</Button>
{/if}
{#if isLoading}
<Skeleton class="h-[70vh] w-full" />
<Skeleton class="h-[70vh] w-full" />
<Skeleton class="h-[70vh] w-full" />
{:else}
{#each media.slice(0, 3) as mediaItem}
<AddMediaCard {isShow} result={mediaItem} />
{/each}
{/if}
{#if isShow}
<Button class="md:col-start-2" variant="secondary" href="/dashboard/tv/add-show">
More recommendations
<ChevronRight />
</Button>
{:else}
<Button class="md:col-start-2" variant="secondary" href="/dashboard/movies/add-movie">
More recommendations
<ChevronRight />
</Button>
{/if}
</div>

View File

@@ -1,128 +1,128 @@
<script lang="ts">
import {env} from '$env/dynamic/public';
import {Button, buttonVariants} from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import {Label} from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select/index.js';
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
import type {PublicMovie, Quality} from '$lib/types.js';
import {getFullyQualifiedMediaName, getTorrentQualityString} from '$lib/utils.js';
import {toast} from 'svelte-sonner';
import { env } from '$env/dynamic/public';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select/index.js';
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
import type { PublicMovie, Quality } from '$lib/types.js';
import { getFullyQualifiedMediaName, getTorrentQualityString } from '$lib/utils.js';
import { toast } from 'svelte-sonner';
const apiUrl = env.PUBLIC_API_URL;
let {movie}: { movie: PublicMovie } = $props();
let dialogOpen = $state(false);
let minQuality = $state<Quality | undefined>(undefined);
let wantedQuality = $state<Quality | undefined>(undefined);
let isSubmittingRequest = $state(false);
let submitRequestError = $state<string | null>(null);
const apiUrl = env.PUBLIC_API_URL;
let { movie }: { movie: PublicMovie } = $props();
let dialogOpen = $state(false);
let minQuality = $state<Quality | undefined>(undefined);
let wantedQuality = $state<Quality | undefined>(undefined);
let isSubmittingRequest = $state(false);
let submitRequestError = $state<string | null>(null);
const qualityValues: Quality[] = [1, 2, 3, 4];
let qualityOptions = $derived(
qualityValues.map((q) => ({value: q, label: getTorrentQualityString(q)}))
);
let isFormInvalid = $derived(!minQuality || !wantedQuality || wantedQuality > minQuality);
const qualityValues: Quality[] = [1, 2, 3, 4];
let qualityOptions = $derived(
qualityValues.map((q) => ({ value: q, label: getTorrentQualityString(q) }))
);
let isFormInvalid = $derived(!minQuality || !wantedQuality || wantedQuality > minQuality);
async function handleRequestMovie() {
isSubmittingRequest = true;
submitRequestError = null;
async function handleRequestMovie() {
isSubmittingRequest = true;
submitRequestError = null;
try {
const response = await fetch(`${apiUrl}/movies/requests`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
movie_id: movie.id,
min_quality: minQuality,
wanted_quality: wantedQuality
})
});
try {
const response = await fetch(`${apiUrl}/movies/requests`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
movie_id: movie.id,
min_quality: minQuality,
wanted_quality: wantedQuality
})
});
if (response.status === 204) {
dialogOpen = false;
minQuality = undefined;
wantedQuality = undefined;
toast.success('Movie request submitted successfully!');
} else {
const errorData = await response.json().catch(() => ({message: response.statusText}));
submitRequestError = `Failed to submit request: ${errorData.message || response.statusText}`;
toast.error(submitRequestError);
console.error('Failed to submit request', response.statusText, errorData);
}
} catch (error) {
submitRequestError = `Error submitting request: ${error instanceof Error ? error.message : String(error)}`;
toast.error(submitRequestError);
console.error('Error submitting request:', error);
} finally {
isSubmittingRequest = false;
}
}
if (response.status === 204) {
dialogOpen = false;
minQuality = undefined;
wantedQuality = undefined;
toast.success('Movie request submitted successfully!');
} else {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
submitRequestError = `Failed to submit request: ${errorData.message || response.statusText}`;
toast.error(submitRequestError);
console.error('Failed to submit request', response.statusText, errorData);
}
} catch (error) {
submitRequestError = `Error submitting request: ${error instanceof Error ? error.message : String(error)}`;
toast.error(submitRequestError);
console.error('Error submitting request:', error);
} finally {
isSubmittingRequest = false;
}
}
</script>
<Dialog.Root bind:open={dialogOpen}>
<Dialog.Trigger
class={buttonVariants({ variant: 'default' })}
on:click={() => {
<Dialog.Trigger
class={buttonVariants({ variant: 'default' })}
on:click={() => {
dialogOpen = true;
}}
>
Request Movie
</Dialog.Trigger>
<Dialog.Content class="max-h-[90vh] w-fit min-w-[clamp(300px,50vw,600px)] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Request {getFullyQualifiedMediaName(movie)}</Dialog.Title>
<Dialog.Description>Select desired qualities to submit a request.</Dialog.Description>
</Dialog.Header>
<div class="grid gap-4 py-4">
<!-- Min Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="min-quality">Min Quality</Label>
<Select.Root bind:value={minQuality} type="single">
<Select.Trigger class="w-full" id="min-quality">
{minQuality ? getTorrentQualityString(minQuality) : 'Select Minimum Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
>
Request Movie
</Dialog.Trigger>
<Dialog.Content class="max-h-[90vh] w-fit min-w-[clamp(300px,50vw,600px)] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Request {getFullyQualifiedMediaName(movie)}</Dialog.Title>
<Dialog.Description>Select desired qualities to submit a request.</Dialog.Description>
</Dialog.Header>
<div class="grid gap-4 py-4">
<!-- Min Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="min-quality">Min Quality</Label>
<Select.Root bind:value={minQuality} type="single">
<Select.Trigger class="w-full" id="min-quality">
{minQuality ? getTorrentQualityString(minQuality) : 'Select Minimum Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Wanted Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="wanted-quality">Wanted Quality</Label>
<Select.Root bind:value={wantedQuality} type="single">
<Select.Trigger class="w-full" id="wanted-quality">
{wantedQuality ? getTorrentQualityString(wantedQuality) : 'Select Wanted Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Wanted Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="wanted-quality">Wanted Quality</Label>
<Select.Root bind:value={wantedQuality} type="single">
<Select.Trigger class="w-full" id="wanted-quality">
{wantedQuality ? getTorrentQualityString(wantedQuality) : 'Select Wanted Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
{#if submitRequestError}
<p class="col-span-full text-center text-sm text-red-500">{submitRequestError}</p>
{/if}
</div>
<Dialog.Footer>
<Button disabled={isSubmittingRequest} onclick={() => (dialogOpen = false)} variant="outline"
>Cancel
</Button>
<Button disabled={isFormInvalid || isSubmittingRequest} onclick={handleRequestMovie}>
{#if isSubmittingRequest}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin"/>
Submitting...
{:else}
Submit Request
{/if}
</Button>
</Dialog.Footer>
</Dialog.Content>
{#if submitRequestError}
<p class="col-span-full text-center text-sm text-red-500">{submitRequestError}</p>
{/if}
</div>
<Dialog.Footer>
<Button disabled={isSubmittingRequest} onclick={() => (dialogOpen = false)} variant="outline"
>Cancel
</Button>
<Button disabled={isFormInvalid || isSubmittingRequest} onclick={handleRequestMovie}>
{#if isSubmittingRequest}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Submitting...
{:else}
Submit Request
{/if}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,167 +1,167 @@
<script lang="ts">
import {env} from '$env/dynamic/public';
import {Button, buttonVariants} from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import {Label} from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select/index.js';
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
import type {CreateSeasonRequest, PublicShow, Quality} from '$lib/types.js';
import {getFullyQualifiedMediaName, getTorrentQualityString} from '$lib/utils.js';
import {toast} from 'svelte-sonner';
import { env } from '$env/dynamic/public';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select/index.js';
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
import type { CreateSeasonRequest, PublicShow, Quality } from '$lib/types.js';
import { getFullyQualifiedMediaName, getTorrentQualityString } from '$lib/utils.js';
import { toast } from 'svelte-sonner';
const apiUrl = env.PUBLIC_API_URL;
let {show}: { show: PublicShow } = $props();
const apiUrl = env.PUBLIC_API_URL;
let { show }: { show: PublicShow } = $props();
let dialogOpen = $state(false);
let selectedSeasonsIds = $state<string[]>([]);
let minQuality = $state<Quality | undefined>(undefined);
let wantedQuality = $state<Quality | undefined>(undefined);
let isSubmittingRequest = $state(false);
let submitRequestError = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedSeasonsIds = $state<string[]>([]);
let minQuality = $state<Quality | undefined>(undefined);
let wantedQuality = $state<Quality | undefined>(undefined);
let isSubmittingRequest = $state(false);
let submitRequestError = $state<string | null>(null);
const qualityValues: Quality[] = [1, 2, 3, 4];
let qualityOptions = $derived(
qualityValues.map((q) => ({value: q, label: getTorrentQualityString(q)}))
);
let isFormInvalid = $derived(
!selectedSeasonsIds ||
selectedSeasonsIds.length === 0 ||
!minQuality ||
!wantedQuality ||
wantedQuality > minQuality
);
const qualityValues: Quality[] = [1, 2, 3, 4];
let qualityOptions = $derived(
qualityValues.map((q) => ({ value: q, label: getTorrentQualityString(q) }))
);
let isFormInvalid = $derived(
!selectedSeasonsIds ||
selectedSeasonsIds.length === 0 ||
!minQuality ||
!wantedQuality ||
wantedQuality > minQuality
);
async function handleRequestSeason() {
isSubmittingRequest = true;
submitRequestError = null;
async function handleRequestSeason() {
isSubmittingRequest = true;
submitRequestError = null;
const payloads: CreateSeasonRequest = selectedSeasonsIds.map((seasonId) => ({
season_id: seasonId,
min_quality: minQuality,
wanted_quality: wantedQuality
}));
for (const payload of payloads) {
try {
const response = await fetch(`${apiUrl}/tv/seasons/requests`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(payload)
});
const payloads: CreateSeasonRequest = selectedSeasonsIds.map((seasonId) => ({
season_id: seasonId,
min_quality: minQuality,
wanted_quality: wantedQuality
}));
for (const payload of payloads) {
try {
const response = await fetch(`${apiUrl}/tv/seasons/requests`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(payload)
});
if (response.status === 204) {
// Success, no content
dialogOpen = false; // Close the dialog
// Reset form fields
selectedSeasonsIds = undefined;
minQuality = undefined;
wantedQuality = undefined;
toast.success('Season request submitted successfully!');
} else {
const errorData = await response.json().catch(() => ({message: response.statusText}));
submitRequestError = `Failed to submit request: ${errorData.message || response.statusText}`;
toast.error(submitRequestError);
console.error('Failed to submit request', response.statusText, errorData);
}
} catch (error) {
submitRequestError = `Error submitting request: ${error instanceof Error ? error.message : String(error)}`;
toast.error(submitRequestError);
console.error('Error submitting request:', error);
} finally {
isSubmittingRequest = false;
}
}
}
if (response.status === 204) {
// Success, no content
dialogOpen = false; // Close the dialog
// Reset form fields
selectedSeasonsIds = undefined;
minQuality = undefined;
wantedQuality = undefined;
toast.success('Season request submitted successfully!');
} else {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
submitRequestError = `Failed to submit request: ${errorData.message || response.statusText}`;
toast.error(submitRequestError);
console.error('Failed to submit request', response.statusText, errorData);
}
} catch (error) {
submitRequestError = `Error submitting request: ${error instanceof Error ? error.message : String(error)}`;
toast.error(submitRequestError);
console.error('Error submitting request:', error);
} finally {
isSubmittingRequest = false;
}
}
}
</script>
<Dialog.Root bind:open={dialogOpen}>
<Dialog.Trigger
class={buttonVariants({ variant: 'default' })}
on:click={() => {
<Dialog.Trigger
class={buttonVariants({ variant: 'default' })}
on:click={() => {
dialogOpen = true;
}}
>
Request Season
</Dialog.Trigger>
<Dialog.Content class="max-h-[90vh] w-fit min-w-[clamp(300px,50vw,600px)] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Request a Season for {getFullyQualifiedMediaName(show)}</Dialog.Title>
<Dialog.Description>
Select a season and desired qualities to submit a request.
</Dialog.Description>
</Dialog.Header>
<div class="grid gap-4 py-4">
<!-- Season Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="season">Season</Label>
<Select.Root bind:value={selectedSeasonsIds}>
<Select.Trigger class="w-full" id="season">
{#each selectedSeasonsIds as seasonId (seasonId)}
{#if show.seasons.find((season) => season.id === seasonId)}
{show.seasons.find((season) => season.id === seasonId).number},&nbsp;
{/if}
{:else}
Select one or more seasons
{/each}
</Select.Trigger>
<Select.Content>
{#each show.seasons as season (season.id)}
<Select.Item value={season.id}>
Season {season.number}{season.name ? `: ${season.name}` : ''}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
>
Request Season
</Dialog.Trigger>
<Dialog.Content class="max-h-[90vh] w-fit min-w-[clamp(300px,50vw,600px)] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Request a Season for {getFullyQualifiedMediaName(show)}</Dialog.Title>
<Dialog.Description>
Select a season and desired qualities to submit a request.
</Dialog.Description>
</Dialog.Header>
<div class="grid gap-4 py-4">
<!-- Season Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="season">Season</Label>
<Select.Root bind:value={selectedSeasonsIds}>
<Select.Trigger class="w-full" id="season">
{#each selectedSeasonsIds as seasonId (seasonId)}
{#if show.seasons.find((season) => season.id === seasonId)}
{show.seasons.find((season) => season.id === seasonId).number},&nbsp;
{/if}
{:else}
Select one or more seasons
{/each}
</Select.Trigger>
<Select.Content>
{#each show.seasons as season (season.id)}
<Select.Item value={season.id}>
Season {season.number}{season.name ? `: ${season.name}` : ''}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Min Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="min-quality">Min Quality</Label>
<Select.Root bind:value={minQuality} type="single">
<Select.Trigger class="w-full" id="min-quality">
{minQuality ? getTorrentQualityString(minQuality) : 'Select Minimum Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Min Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="min-quality">Min Quality</Label>
<Select.Root bind:value={minQuality} type="single">
<Select.Trigger class="w-full" id="min-quality">
{minQuality ? getTorrentQualityString(minQuality) : 'Select Minimum Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Wanted Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="wanted-quality">Wanted Quality</Label>
<Select.Root bind:value={wantedQuality} type="single">
<Select.Trigger class="w-full" id="wanted-quality">
{wantedQuality ? getTorrentQualityString(wantedQuality) : 'Select Wanted Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Wanted Quality Select -->
<div class="grid grid-cols-[1fr,3fr] items-center gap-4 md:grid-cols-[100px,1fr]">
<Label class="text-right" for="wanted-quality">Wanted Quality</Label>
<Select.Root bind:value={wantedQuality} type="single">
<Select.Trigger class="w-full" id="wanted-quality">
{wantedQuality ? getTorrentQualityString(wantedQuality) : 'Select Wanted Quality'}
</Select.Trigger>
<Select.Content>
{#each qualityOptions as option (option.value)}
<Select.Item value={option.value}>{option.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
{#if submitRequestError}
<p class="col-span-full text-center text-sm text-red-500">{submitRequestError}</p>
{/if}
</div>
<Dialog.Footer>
<Button disabled={isSubmittingRequest} onclick={() => (dialogOpen = false)} variant="outline"
>Cancel
</Button>
<Button disabled={isFormInvalid || isSubmittingRequest} onclick={handleRequestSeason}>
{#if isSubmittingRequest}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin"/>
Submitting...
{:else}
Submit Request
{/if}
</Button>
</Dialog.Footer>
</Dialog.Content>
{#if submitRequestError}
<p class="col-span-full text-center text-sm text-red-500">{submitRequestError}</p>
{/if}
</div>
<Dialog.Footer>
<Button disabled={isSubmittingRequest} onclick={() => (dialogOpen = false)} variant="outline"
>Cancel
</Button>
<Button disabled={isFormInvalid || isSubmittingRequest} onclick={handleRequestSeason}>
{#if isSubmittingRequest}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Submitting...
{:else}
Submit Request
{/if}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,186 +1,186 @@
<script lang="ts">
import {getFullyQualifiedMediaName, getTorrentQualityString} from '$lib/utils.js';
import type {SeasonRequest, User} from '$lib/types.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import * as Table from '$lib/components/ui/table/index.js';
import {getContext} from 'svelte';
import {Button} from '$lib/components/ui/button/index.js';
import {env} from '$env/dynamic/public';
import {toast} from 'svelte-sonner';
import {goto} from '$app/navigation';
import {base} from '$app/paths';
import { getFullyQualifiedMediaName, getTorrentQualityString } from '$lib/utils.js';
import type { SeasonRequest, User } from '$lib/types.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import * as Table from '$lib/components/ui/table/index.js';
import { getContext } from 'svelte';
import { Button } from '$lib/components/ui/button/index.js';
import { env } from '$env/dynamic/public';
import { toast } from 'svelte-sonner';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
const apiUrl = env.PUBLIC_API_URL;
let {
requests,
filter = () => {
return true;
},
isShow = true
}: {
requests: SeasonRequest[];
filter: (request: SeasonRequest) => boolean;
isShow: boolean;
} = $props();
const user: () => User = getContext('user');
const apiUrl = env.PUBLIC_API_URL;
let {
requests,
filter = () => {
return true;
},
isShow = true
}: {
requests: SeasonRequest[];
filter: (request: SeasonRequest) => boolean;
isShow: boolean;
} = $props();
const user: () => User = getContext('user');
async function approveRequest(requestId: string, currentAuthorizedStatus: boolean) {
try {
const response = await fetch(
`${apiUrl}${isShow ? '/tv/seasons' : '/movies'}/requests/${requestId}?authorized_status=${!currentAuthorizedStatus}`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}
);
async function approveRequest(requestId: string, currentAuthorizedStatus: boolean) {
try {
const response = await fetch(
`${apiUrl}${isShow ? '/tv/seasons' : '/movies'}/requests/${requestId}?authorized_status=${!currentAuthorizedStatus}`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}
);
if (response.ok) {
const requestIndex = requests.findIndex((r) => r.id === requestId);
if (requestIndex !== -1) {
let newAuthorizedStatus = !currentAuthorizedStatus;
requests[requestIndex].authorized = newAuthorizedStatus;
requests[requestIndex].authorized_by = newAuthorizedStatus ? user() : null;
}
toast.success(
`Request ${!currentAuthorizedStatus ? 'approved' : 'unapproved'} successfully.`
);
} else {
const errorText = await response.text();
console.error(`Failed to update request status ${response.statusText}`, errorText);
toast.error(`Failed to update request status: ${response.statusText}`);
}
} catch (error) {
console.error('Error updating request status:', error);
toast.error(
'Error updating request status: ' + (error instanceof Error ? error.message : String(error))
);
}
}
if (response.ok) {
const requestIndex = requests.findIndex((r) => r.id === requestId);
if (requestIndex !== -1) {
let newAuthorizedStatus = !currentAuthorizedStatus;
requests[requestIndex].authorized = newAuthorizedStatus;
requests[requestIndex].authorized_by = newAuthorizedStatus ? user() : null;
}
toast.success(
`Request ${!currentAuthorizedStatus ? 'approved' : 'unapproved'} successfully.`
);
} else {
const errorText = await response.text();
console.error(`Failed to update request status ${response.statusText}`, errorText);
toast.error(`Failed to update request status: ${response.statusText}`);
}
} catch (error) {
console.error('Error updating request status:', error);
toast.error(
'Error updating request status: ' + (error instanceof Error ? error.message : String(error))
);
}
}
async function deleteRequest(requestId: string) {
try {
const response = await fetch(
`${apiUrl}${isShow ? '/tv/seasons' : '/movies'}/requests/${requestId}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}
);
async function deleteRequest(requestId: string) {
try {
const response = await fetch(
`${apiUrl}${isShow ? '/tv/seasons' : '/movies'}/requests/${requestId}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}
);
if (response.ok) {
const index = requests.findIndex((r) => r.id === requestId);
if (index > -1) {
requests.splice(index, 1); // Remove the request from the list
}
toast.success('Request deleted successfully');
} else {
console.error(`Failed to delete request ${response.statusText}`, await response.text());
toast.error('Failed to delete request');
}
} catch (error) {
console.error('Error deleting request:', error);
toast.error(
'Error deleting request: ' + (error instanceof Error ? error.message : String(error))
);
}
}
if (response.ok) {
const index = requests.findIndex((r) => r.id === requestId);
if (index > -1) {
requests.splice(index, 1); // Remove the request from the list
}
toast.success('Request deleted successfully');
} else {
console.error(`Failed to delete request ${response.statusText}`, await response.text());
toast.error('Failed to delete request');
}
} catch (error) {
console.error('Error deleting request:', error);
toast.error(
'Error deleting request: ' + (error instanceof Error ? error.message : String(error))
);
}
}
</script>
<Table.Root>
<Table.Caption>A list of all requests.</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>{isShow ? 'Show' : 'Movie'}</Table.Head>
{#if isShow}
<Table.Head>Season</Table.Head>
{/if}
<Table.Head>Minimum Quality</Table.Head>
<Table.Head>Wanted Quality</Table.Head>
<Table.Head>Requested by</Table.Head>
<Table.Head>Approved</Table.Head>
<Table.Head>Approved by</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each requests as request (request.id)}
{#if filter(request)}
<Table.Row>
<Table.Cell>
{#if isShow}
{getFullyQualifiedMediaName(request.show)}
{:else}
{getFullyQualifiedMediaName(request.movie)}
{/if}
</Table.Cell>
{#if isShow}
<Table.Cell>
{request.season.number}
</Table.Cell>
{/if}
<Table.Cell>
{getTorrentQualityString(request.min_quality)}
</Table.Cell>
<Table.Cell>
{getTorrentQualityString(request.wanted_quality)}
</Table.Cell>
<Table.Cell>
{request.requested_by?.email ?? 'N/A'}
</Table.Cell>
<Table.Cell>
<CheckmarkX state={request.authorized}/>
</Table.Cell>
<Table.Cell>
{request.authorized_by?.email ?? 'N/A'}
</Table.Cell>
<!-- TODO: ADD DIALOGUE TO MODIFY REQUEST -->
<Table.Cell class="flex max-w-[150px] flex-col gap-1">
{#if user().is_superuser}
<Button
class=""
size="sm"
onclick={() => approveRequest(request.id, request.authorized)}
>
{request.authorized ? 'Unapprove' : 'Approve'}
</Button>
{#if isShow}
<Button
class=""
size="sm"
variant="outline"
onclick={() => goto(base + '/dashboard/tv/' + request.show.id)}
>
Download manually
</Button>
{:else}
<Button
class=""
size="sm"
variant="outline"
onclick={() => goto(base + '/dashboard/movies/' + request.movie.id)}
>
Download manually
</Button>
{/if}
{/if}
{#if user().is_superuser || user().id === request.requested_by?.id}
<Button variant="destructive" size="sm" onclick={() => deleteRequest(request.id)}
>Delete
</Button>
{/if}
</Table.Cell>
</Table.Row>
{/if}
{:else}
<Table.Row>
<Table.Cell colspan="8" class="text-center">There are currently no requests.</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
<Table.Caption>A list of all requests.</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>{isShow ? 'Show' : 'Movie'}</Table.Head>
{#if isShow}
<Table.Head>Season</Table.Head>
{/if}
<Table.Head>Minimum Quality</Table.Head>
<Table.Head>Wanted Quality</Table.Head>
<Table.Head>Requested by</Table.Head>
<Table.Head>Approved</Table.Head>
<Table.Head>Approved by</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each requests as request (request.id)}
{#if filter(request)}
<Table.Row>
<Table.Cell>
{#if isShow}
{getFullyQualifiedMediaName(request.show)}
{:else}
{getFullyQualifiedMediaName(request.movie)}
{/if}
</Table.Cell>
{#if isShow}
<Table.Cell>
{request.season.number}
</Table.Cell>
{/if}
<Table.Cell>
{getTorrentQualityString(request.min_quality)}
</Table.Cell>
<Table.Cell>
{getTorrentQualityString(request.wanted_quality)}
</Table.Cell>
<Table.Cell>
{request.requested_by?.email ?? 'N/A'}
</Table.Cell>
<Table.Cell>
<CheckmarkX state={request.authorized} />
</Table.Cell>
<Table.Cell>
{request.authorized_by?.email ?? 'N/A'}
</Table.Cell>
<!-- TODO: ADD DIALOGUE TO MODIFY REQUEST -->
<Table.Cell class="flex max-w-[150px] flex-col gap-1">
{#if user().is_superuser}
<Button
class=""
size="sm"
onclick={() => approveRequest(request.id, request.authorized)}
>
{request.authorized ? 'Unapprove' : 'Approve'}
</Button>
{#if isShow}
<Button
class=""
size="sm"
variant="outline"
onclick={() => goto(base + '/dashboard/tv/' + request.show.id)}
>
Download manually
</Button>
{:else}
<Button
class=""
size="sm"
variant="outline"
onclick={() => goto(base + '/dashboard/movies/' + request.movie.id)}
>
Download manually
</Button>
{/if}
{/if}
{#if user().is_superuser || user().id === request.requested_by?.id}
<Button variant="destructive" size="sm" onclick={() => deleteRequest(request.id)}
>Delete
</Button>
{/if}
</Table.Cell>
</Table.Row>
{/if}
{:else}
<Table.Row>
<Table.Cell colspan="8" class="text-center">There are currently no requests.</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>

View File

@@ -1,65 +1,65 @@
<script lang="ts">
import {
convertTorrentSeasonRangeToIntegerRange,
getTorrentQualityString,
getTorrentStatusString
} from '$lib/utils.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import * as Table from '$lib/components/ui/table/index.js';
import {
convertTorrentSeasonRangeToIntegerRange,
getTorrentQualityString,
getTorrentStatusString
} from '$lib/utils.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import * as Table from '$lib/components/ui/table/index.js';
let {torrents, isShow = true} = $props();
let { torrents, isShow = true } = $props();
</script>
<Table.Root>
<Table.Caption>A list of all torrents.</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>Name</Table.Head>
{#if isShow}
<Table.Head>Seasons</Table.Head>
{/if}
<Table.Head>Download Status</Table.Head>
<Table.Head>Quality</Table.Head>
<Table.Head>File Path Suffix</Table.Head>
<Table.Head>Imported</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each torrents as torrent}
<Table.Row>
<Table.Cell class="font-medium">
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{isShow ? torrent.torrent_title : torrent.title}
</a>
</Table.Cell>
{#if isShow}
<Table.Cell>
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{convertTorrentSeasonRangeToIntegerRange(torrent)}
</a>
</Table.Cell>
{/if}
<Table.Cell>
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{getTorrentStatusString(torrent.status)}
</a>
</Table.Cell>
<Table.Cell class="font-medium">
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{getTorrentQualityString(torrent.quality)}
</a>
</Table.Cell>
<Table.Cell class="font-medium">
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{torrent.file_path_suffix}
</a>
</Table.Cell>
<Table.Cell>
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
<CheckmarkX state={torrent.imported}/>
</a>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
<Table.Caption>A list of all torrents.</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>Name</Table.Head>
{#if isShow}
<Table.Head>Seasons</Table.Head>
{/if}
<Table.Head>Download Status</Table.Head>
<Table.Head>Quality</Table.Head>
<Table.Head>File Path Suffix</Table.Head>
<Table.Head>Imported</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each torrents as torrent}
<Table.Row>
<Table.Cell class="font-medium">
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{isShow ? torrent.torrent_title : torrent.title}
</a>
</Table.Cell>
{#if isShow}
<Table.Cell>
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{convertTorrentSeasonRangeToIntegerRange(torrent)}
</a>
</Table.Cell>
{/if}
<Table.Cell>
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{getTorrentStatusString(torrent.status)}
</a>
</Table.Cell>
<Table.Cell class="font-medium">
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{getTorrentQualityString(torrent.quality)}
</a>
</Table.Cell>
<Table.Cell class="font-medium">
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
{torrent.file_path_suffix}
</a>
</Table.Cell>
<Table.Cell>
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
<CheckmarkX state={torrent.imported} />
</a>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>

View File

@@ -1,24 +1,24 @@
<script lang="ts">
import {Accordion as AccordionPrimitive, type WithoutChild} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Accordion as AccordionPrimitive, type WithoutChild } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
</script>
<AccordionPrimitive.Content
bind:ref
class={cn(
bind:ref
class={cn(
'overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
className
)}
{...restProps}
{...restProps}
>
<div class="pb-4 pt-0">
{@render children?.()}
</div>
<div class="pb-4 pt-0">
{@render children?.()}
</div>
</AccordionPrimitive.Content>

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import {Accordion as AccordionPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Accordion as AccordionPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: AccordionPrimitive.ItemProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: AccordionPrimitive.ItemProps = $props();
</script>
<AccordionPrimitive.Item {...restProps} bind:ref class={cn('border-b', className)}/>
<AccordionPrimitive.Item {...restProps} bind:ref class={cn('border-b', className)} />

View File

@@ -1,29 +1,29 @@
<script lang="ts">
import {Accordion as AccordionPrimitive, type WithoutChild} from 'bits-ui';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import {cn} from '$lib/utils.js';
import { Accordion as AccordionPrimitive, type WithoutChild } from 'bits-ui';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
level = 3,
children,
...restProps
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
level?: AccordionPrimitive.HeaderProps['level'];
} = $props();
let {
ref = $bindable(null),
class: className,
level = 3,
children,
...restProps
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
level?: AccordionPrimitive.HeaderProps['level'];
} = $props();
</script>
<AccordionPrimitive.Header class="flex" {level}>
<AccordionPrimitive.Trigger
bind:ref
class={cn(
<AccordionPrimitive.Trigger
bind:ref
class={cn(
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
className
)}
{...restProps}
>
{@render children?.()}
<ChevronDown class="size-4 shrink-0 text-muted-foreground transition-transform duration-200"/>
</AccordionPrimitive.Trigger>
{...restProps}
>
{@render children?.()}
<ChevronDown class="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>

View File

@@ -1,17 +1,17 @@
import {Accordion as AccordionPrimitive} from 'bits-ui';
import { Accordion as AccordionPrimitive } from 'bits-ui';
import Content from './accordion-content.svelte';
import Item from './accordion-item.svelte';
import Trigger from './accordion-trigger.svelte';
const Root = AccordionPrimitive.Root;
export {
Root,
Content,
Item,
Trigger,
//
Root as Accordion,
Content as AccordionContent,
Item as AccordionItem,
Trigger as AccordionTrigger
Root,
Content,
Item,
Trigger,
//
Root as Accordion,
Content as AccordionContent,
Item as AccordionItem,
Trigger as AccordionTrigger
};

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {Avatar as AvatarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
class: className,
ref = $bindable(null),
...restProps
}: AvatarPrimitive.FallbackProps = $props();
let {
class: className,
ref = $bindable(null),
...restProps
}: AvatarPrimitive.FallbackProps = $props();
</script>
<AvatarPrimitive.Fallback
bind:ref
class={cn('flex size-full items-center justify-center bg-muted', className)}
{...restProps}
bind:ref
class={cn('flex size-full items-center justify-center bg-muted', className)}
{...restProps}
/>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import {Avatar as AvatarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
class: className,
src,
alt,
ref = $bindable(null),
...restProps
}: AvatarPrimitive.ImageProps = $props();
let {
class: className,
src,
alt,
ref = $bindable(null),
...restProps
}: AvatarPrimitive.ImageProps = $props();
</script>
<AvatarPrimitive.Image
bind:ref
{src}
{alt}
class={cn('aspect-square size-full', className)}
{...restProps}
bind:ref
{src}
{alt}
class={cn('aspect-square size-full', className)}
{...restProps}
/>

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import {Avatar as AvatarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
class: className,
ref = $bindable(null),
loadingStatus = $bindable('loading'),
...restProps
}: AvatarPrimitive.RootProps = $props();
let {
class: className,
ref = $bindable(null),
loadingStatus = $bindable('loading'),
...restProps
}: AvatarPrimitive.RootProps = $props();
</script>
<AvatarPrimitive.Root
bind:loadingStatus
bind:ref
class={cn('relative flex size-10 shrink-0 overflow-hidden rounded-full', className)}
{...restProps}
bind:loadingStatus
bind:ref
class={cn('relative flex size-10 shrink-0 overflow-hidden rounded-full', className)}
{...restProps}
/>

View File

@@ -3,11 +3,11 @@ import Image from './avatar-image.svelte';
import Fallback from './avatar-fallback.svelte';
export {
Root,
Image,
Fallback,
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback
Root,
Image,
Fallback,
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback
};

View File

@@ -1,49 +1,49 @@
<script lang="ts" module>
import {type VariantProps, tv} from 'tailwind-variants';
import { type VariantProps, tv } from 'tailwind-variants';
export const badgeVariants = tv({
base: 'focus:ring-ring inline-flex select-none items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent shadow',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent shadow',
outline: 'text-foreground'
}
},
defaultVariants: {
variant: 'default'
}
});
export const badgeVariants = tv({
base: 'focus:ring-ring inline-flex select-none items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent shadow',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent shadow',
outline: 'text-foreground'
}
},
defaultVariants: {
variant: 'default'
}
});
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
</script>
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAnchorAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAnchorAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
href,
class: className,
variant = 'default',
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
} = $props();
let {
ref = $bindable(null),
href,
class: className,
variant = 'default',
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
} = $props();
</script>
<svelte:element
this={href ? 'a' : 'span'}
{...restProps}
bind:this={ref}
class={cn(badgeVariants({ variant }), className)}
{href}
this={href ? 'a' : 'span'}
{...restProps}
bind:this={ref}
class={cn(badgeVariants({ variant }), className)}
{href}
>
{@render children?.()}
{@render children?.()}
</svelte:element>

View File

@@ -1,2 +1,2 @@
export {default as Badge} from './badge.svelte';
export {badgeVariants, type BadgeVariant} from './badge.svelte';
export { default as Badge } from './badge.svelte';
export { badgeVariants, type BadgeVariant } from './badge.svelte';

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import Ellipsis from '@lucide/svelte/icons/ellipsis';
import type {WithElementRef, WithoutChildren} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef, WithoutChildren } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,12 +12,12 @@
</script>
<span
{...restProps}
aria-hidden="true"
bind:this={ref}
class={cn('flex size-9 items-center justify-center', className)}
role="presentation"
{...restProps}
aria-hidden="true"
bind:this={ref}
class={cn('flex size-9 items-center justify-center', className)}
role="presentation"
>
<Ellipsis class="size-4"/>
<Ellipsis class="size-4" />
<span class="sr-only">More</span>
</span>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLLiAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLLiAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li bind:this={ref} class={cn('inline-flex items-center gap-1.5', className)} {...restProps}>
{@render children?.()}
{@render children?.()}
</li>

View File

@@ -1,31 +1,31 @@
<script lang="ts">
import type {HTMLAnchorAttributes} from 'svelte/elements';
import type {Snippet} from 'svelte';
import type {WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
import type { HTMLAnchorAttributes } from 'svelte/elements';
import type { Snippet } from 'svelte';
import type { WithElementRef } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
href = undefined,
child,
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
} = $props();
let {
ref = $bindable(null),
class: className,
href = undefined,
child,
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
} = $props();
const attrs = $derived({
class: cn('hover:text-foreground transition-colors', className),
href,
...restProps
});
const attrs = $derived({
class: cn('hover:text-foreground transition-colors', className),
href,
...restProps
});
</script>
{#if child}
{@render child({props: attrs})}
{@render child({ props: attrs })}
{:else}
<a bind:this={ref} {...attrs}>
{@render children?.()}
</a>
<a bind:this={ref} {...attrs}>
{@render children?.()}
</a>
{/if}

View File

@@ -1,23 +1,23 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLOlAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLOlAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLOlAttributes> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLOlAttributes> = $props();
</script>
<ol
bind:this={ref}
class={cn(
bind:this={ref}
class={cn(
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
className
)}
{...restProps}
{...restProps}
>
{@render children?.()}
{@render children?.()}
</ol>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,12 +12,12 @@
</script>
<span
{...restProps}
aria-current="page"
aria-disabled="true"
bind:this={ref}
class={cn('font-normal text-foreground', className)}
role="link"
{...restProps}
aria-current="page"
aria-disabled="true"
bind:this={ref}
class={cn('font-normal text-foreground', className)}
role="link"
>
{@render children?.()}
</span>

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import type {WithElementRef} from 'bits-ui';
import type {HTMLLiAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import type { WithElementRef } from 'bits-ui';
import type { HTMLLiAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li
role="presentation"
aria-hidden="true"
class={cn('[&>svg]:size-3.5', className)}
bind:this={ref}
{...restProps}
role="presentation"
aria-hidden="true"
class={cn('[&>svg]:size-3.5', className)}
bind:this={ref}
{...restProps}
>
{#if children}
{@render children?.()}
{:else}
<ChevronRight/>
{/if}
{#if children}
{@render children?.()}
{:else}
<ChevronRight />
{/if}
</li>

View File

@@ -1,15 +1,15 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
let {
ref = $bindable(),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script>
<nav class={className} bind:this={ref} aria-label="breadcrumb" {...restProps}>
{@render children?.()}
{@render children?.()}
</nav>

View File

@@ -7,19 +7,19 @@ import List from './breadcrumb-list.svelte';
import Page from './breadcrumb-page.svelte';
export {
Root,
Ellipsis,
Item,
Separator,
Link,
List,
Page,
//
Root as Breadcrumb,
Ellipsis as BreadcrumbEllipsis,
Item as BreadcrumbItem,
Separator as BreadcrumbSeparator,
Link as BreadcrumbLink,
List as BreadcrumbList,
Page as BreadcrumbPage
Root,
Ellipsis,
Item,
Separator,
Link,
List,
Page,
//
Root as Breadcrumb,
Ellipsis as BreadcrumbEllipsis,
Item as BreadcrumbItem,
Separator as BreadcrumbSeparator,
Link as BreadcrumbLink,
List as BreadcrumbList,
Page as BreadcrumbPage
};

View File

@@ -1,7 +1,7 @@
<script lang="ts" module>
import type {WithElementRef} from 'bits-ui';
import type {HTMLAnchorAttributes, HTMLButtonAttributes} from 'svelte/elements';
import {type VariantProps, tv} from 'tailwind-variants';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({
base: 'focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
@@ -10,7 +10,7 @@
default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm',
outline:
'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm',
'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline'
@@ -32,14 +32,14 @@
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
import {cn} from '$lib/utils.js';
import { cn } from '$lib/utils.js';
let {
class: className,
@@ -59,10 +59,10 @@
</a>
{:else}
<button
bind:this={ref}
class={cn(buttonVariants({ variant, size }), className)}
{type}
{...restProps}
bind:this={ref}
class={cn(buttonVariants({ variant, size }), className)}
{type}
{...restProps}
>
{@render children?.()}
</button>

View File

@@ -1,17 +1,17 @@
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
} from './button.svelte';
export {
Root,
type ButtonProps as Props,
//
Root as Button,
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant
Root,
type ButtonProps as Props,
//
Root as Button,
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant
};

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div {...restProps} bind:this={ref} class={cn('p-6', className)}>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
</script>
<p {...restProps} bind:this={ref} class={cn('text-sm text-muted-foreground', className)}>
{@render children?.()}
{@render children?.()}
</p>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div {...restProps} bind:this={ref} class={cn('flex items-center p-6 pt-0', className)}>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div {...restProps} bind:this={ref} class={cn('flex flex-col space-y-1.5 p-6 pb-0', className)}>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,25 +1,25 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
level = 3,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
level?: 1 | 2 | 3 | 4 | 5 | 6;
} = $props();
let {
ref = $bindable(null),
class: className,
level = 3,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
level?: 1 | 2 | 3 | 4 | 5 | 6;
} = $props();
</script>
<div
{...restProps}
aria-level={level}
bind:this={ref}
class={cn('font-semibold leading-none tracking-tight', className)}
role="heading"
{...restProps}
aria-level={level}
bind:this={ref}
class={cn('font-semibold leading-none tracking-tight', className)}
role="heading"
>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
{...restProps}
bind:this={ref}
class={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
{...restProps}
bind:this={ref}
class={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -6,17 +6,17 @@ import Header from './card-header.svelte';
import Title from './card-title.svelte';
export {
Root,
Content,
Description,
Footer,
Header,
Title,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle
Root,
Content,
Description,
Footer,
Header,
Title,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle
};

View File

@@ -1,25 +1,25 @@
<script lang="ts">
import emblaCarouselSvelte from 'embla-carousel-svelte';
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {getEmblaContext} from './context.js';
import {cn} from '$lib/utils.js';
import emblaCarouselSvelte from 'embla-carousel-svelte';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { getEmblaContext } from './context.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
const emblaCtx = getEmblaContext('<Carousel.Content/>');
const emblaCtx = getEmblaContext('<Carousel.Content/>');
</script>
<!-- svelte-ignore event_directive_deprecated -->
<div
class="overflow-hidden"
on:emblaInit={emblaCtx.onInit}
use:emblaCarouselSvelte={{
class="overflow-hidden"
on:emblaInit={emblaCtx.onInit}
use:emblaCarouselSvelte={{
options: {
container: '[data-embla-container]',
slides: '[data-embla-slide]',
@@ -29,16 +29,16 @@
plugins: emblaCtx.plugins
}}
>
<div
{...restProps}
bind:this={ref}
class={cn(
<div
{...restProps}
bind:this={ref}
class={cn(
'flex',
emblaCtx.orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
className
)}
data-embla-container=""
>
{@render children?.()}
</div>
data-embla-container=""
>
{@render children?.()}
</div>
</div>

View File

@@ -1,30 +1,30 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {getEmblaContext} from './context.js';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { getEmblaContext } from './context.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
const emblaCtx = getEmblaContext('<Carousel.Item/>');
const emblaCtx = getEmblaContext('<Carousel.Item/>');
</script>
<div
{...restProps}
aria-roledescription="slide"
bind:this={ref}
class={cn(
{...restProps}
aria-roledescription="slide"
bind:this={ref}
class={cn(
'min-w-0 shrink-0 grow-0 basis-full',
emblaCtx.orientation === 'horizontal' ? 'pl-4' : 'pt-4',
className
)}
data-embla-slide=""
role="group"
data-embla-slide=""
role="group"
>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,37 +1,37 @@
<script lang="ts">
import ArrowRight from '@lucide/svelte/icons/arrow-right';
import type {WithoutChildren} from 'bits-ui';
import {getEmblaContext} from './context.js';
import {cn} from '$lib/utils.js';
import {Button, type Props} from '$lib/components/ui/button/index.js';
import ArrowRight from '@lucide/svelte/icons/arrow-right';
import type { WithoutChildren } from 'bits-ui';
import { getEmblaContext } from './context.js';
import { cn } from '$lib/utils.js';
import { Button, type Props } from '$lib/components/ui/button/index.js';
let {
ref = $bindable(null),
class: className,
variant = 'outline',
size = 'icon',
...restProps
}: WithoutChildren<Props> = $props();
let {
ref = $bindable(null),
class: className,
variant = 'outline',
size = 'icon',
...restProps
}: WithoutChildren<Props> = $props();
const emblaCtx = getEmblaContext('<Carousel.Next/>');
const emblaCtx = getEmblaContext('<Carousel.Next/>');
</script>
<Button
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'absolute size-8 touch-manipulation rounded-full',
emblaCtx.orientation === 'horizontal'
? '-right-12 top-1/2 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
className
)}
disabled={!emblaCtx.canScrollNext}
onclick={emblaCtx.scrollNext}
onkeydown={emblaCtx.handleKeyDown}
{size}
{variant}
disabled={!emblaCtx.canScrollNext}
onclick={emblaCtx.scrollNext}
onkeydown={emblaCtx.handleKeyDown}
{size}
{variant}
>
<ArrowRight/>
<span class="sr-only">Next slide</span>
<ArrowRight />
<span class="sr-only">Next slide</span>
</Button>

View File

@@ -1,37 +1,37 @@
<script lang="ts">
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import type {WithoutChildren} from 'bits-ui';
import {getEmblaContext} from './context.js';
import {cn} from '$lib/utils.js';
import {Button, type Props} from '$lib/components/ui/button/index.js';
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import type { WithoutChildren } from 'bits-ui';
import { getEmblaContext } from './context.js';
import { cn } from '$lib/utils.js';
import { Button, type Props } from '$lib/components/ui/button/index.js';
let {
ref = $bindable(null),
class: className,
variant = 'outline',
size = 'icon',
...restProps
}: WithoutChildren<Props> = $props();
let {
ref = $bindable(null),
class: className,
variant = 'outline',
size = 'icon',
...restProps
}: WithoutChildren<Props> = $props();
const emblaCtx = getEmblaContext('<Carousel.Previous/>');
const emblaCtx = getEmblaContext('<Carousel.Previous/>');
</script>
<Button
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'absolute size-8 touch-manipulation rounded-full',
emblaCtx.orientation === 'horizontal'
? '-left-12 top-1/2 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
className
)}
disabled={!emblaCtx.canScrollPrev}
onclick={emblaCtx.scrollPrev}
onkeydown={emblaCtx.handleKeyDown}
{size}
{variant}
disabled={!emblaCtx.canScrollPrev}
onclick={emblaCtx.scrollPrev}
onkeydown={emblaCtx.handleKeyDown}
{size}
{variant}
>
<ArrowLeft/>
<span class="sr-only">Previous slide</span>
<ArrowLeft />
<span class="sr-only">Previous slide</span>
</Button>

View File

@@ -1,95 +1,94 @@
<script lang="ts">
import {
type CarouselAPI,
type CarouselProps,
type EmblaContext,
setEmblaContext
} from './context.js';
import {cn} from '$lib/utils.js';
import {
type CarouselAPI,
type CarouselProps,
type EmblaContext,
setEmblaContext
} from './context.js';
import { cn } from '$lib/utils.js';
let {
opts = {},
plugins = [],
setApi = () => {
},
orientation = 'horizontal',
class: className,
children,
...restProps
}: CarouselProps = $props();
let {
opts = {},
plugins = [],
setApi = () => {},
orientation = 'horizontal',
class: className,
children,
...restProps
}: CarouselProps = $props();
let carouselState = $state<EmblaContext>({
api: undefined,
scrollPrev,
scrollNext,
orientation,
canScrollNext: false,
canScrollPrev: false,
handleKeyDown,
options: opts,
plugins,
onInit,
scrollSnaps: [],
selectedIndex: 0,
scrollTo
});
let carouselState = $state<EmblaContext>({
api: undefined,
scrollPrev,
scrollNext,
orientation,
canScrollNext: false,
canScrollPrev: false,
handleKeyDown,
options: opts,
plugins,
onInit,
scrollSnaps: [],
selectedIndex: 0,
scrollTo
});
setEmblaContext(carouselState);
setEmblaContext(carouselState);
function scrollPrev() {
carouselState.api?.scrollPrev();
}
function scrollPrev() {
carouselState.api?.scrollPrev();
}
function scrollNext() {
carouselState.api?.scrollNext();
}
function scrollNext() {
carouselState.api?.scrollNext();
}
function scrollTo(index: number, jump?: boolean) {
carouselState.api?.scrollTo(index, jump);
}
function scrollTo(index: number, jump?: boolean) {
carouselState.api?.scrollTo(index, jump);
}
function onSelect(api: CarouselAPI) {
if (!api) return;
carouselState.canScrollPrev = api.canScrollPrev();
carouselState.canScrollNext = api.canScrollNext();
carouselState.selectedIndex = api.selectedScrollSnap();
}
function onSelect(api: CarouselAPI) {
if (!api) return;
carouselState.canScrollPrev = api.canScrollPrev();
carouselState.canScrollNext = api.canScrollNext();
carouselState.selectedIndex = api.selectedScrollSnap();
}
$effect(() => {
if (carouselState.api) {
onSelect(carouselState.api);
carouselState.api.on('select', onSelect);
carouselState.api.on('reInit', onSelect);
}
});
$effect(() => {
if (carouselState.api) {
onSelect(carouselState.api);
carouselState.api.on('select', onSelect);
carouselState.api.on('reInit', onSelect);
}
});
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'ArrowLeft') {
e.preventDefault();
scrollPrev();
} else if (e.key === 'ArrowRight') {
e.preventDefault();
scrollNext();
}
}
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'ArrowLeft') {
e.preventDefault();
scrollPrev();
} else if (e.key === 'ArrowRight') {
e.preventDefault();
scrollNext();
}
}
$effect(() => {
setApi(carouselState.api);
});
$effect(() => {
setApi(carouselState.api);
});
function onInit(event: CustomEvent<CarouselAPI>) {
carouselState.api = event.detail;
function onInit(event: CustomEvent<CarouselAPI>) {
carouselState.api = event.detail;
carouselState.scrollSnaps = carouselState.api.scrollSnapList();
}
carouselState.scrollSnaps = carouselState.api.scrollSnapList();
}
$effect(() => {
return () => {
carouselState.api?.off('select', onSelect);
};
});
$effect(() => {
return () => {
carouselState.api?.off('select', onSelect);
};
});
</script>
<div {...restProps} aria-roledescription="carousel" class={cn('relative', className)} role="region">
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,15 +1,15 @@
import type {EmblaCarouselSvelteType} from 'embla-carousel-svelte';
import type { EmblaCarouselSvelteType } from 'embla-carousel-svelte';
import type emblaCarouselSvelte from 'embla-carousel-svelte';
import {getContext, hasContext, setContext} from 'svelte';
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import { getContext, hasContext, setContext } from 'svelte';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
export type CarouselAPI =
NonNullable<NonNullable<EmblaCarouselSvelteType['$$_attributes']>['on:emblaInit']> extends (
evt: CustomEvent<infer CarouselAPI>
) => void
? CarouselAPI
: never;
NonNullable<NonNullable<EmblaCarouselSvelteType['$$_attributes']>['on:emblaInit']> extends (
evt: CustomEvent<infer CarouselAPI>
) => void
? CarouselAPI
: never;
type EmblaCarouselConfig = NonNullable<Parameters<typeof emblaCarouselSvelte>[1]>;
@@ -19,38 +19,38 @@ export type CarouselPlugins = EmblaCarouselConfig['plugins'];
////
export type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugins;
setApi?: (api: CarouselAPI | undefined) => void;
orientation?: 'horizontal' | 'vertical';
opts?: CarouselOptions;
plugins?: CarouselPlugins;
setApi?: (api: CarouselAPI | undefined) => void;
orientation?: 'horizontal' | 'vertical';
} & WithElementRef<HTMLAttributes<HTMLDivElement>>;
const EMBLA_CAROUSEL_CONTEXT = Symbol('EMBLA_CAROUSEL_CONTEXT');
export type EmblaContext = {
api: CarouselAPI | undefined;
orientation: 'horizontal' | 'vertical';
scrollNext: () => void;
scrollPrev: () => void;
canScrollNext: boolean;
canScrollPrev: boolean;
handleKeyDown: (e: KeyboardEvent) => void;
options: CarouselOptions;
plugins: CarouselPlugins;
onInit: (e: CustomEvent<CarouselAPI>) => void;
scrollTo: (index: number, jump?: boolean) => void;
scrollSnaps: number[];
selectedIndex: number;
api: CarouselAPI | undefined;
orientation: 'horizontal' | 'vertical';
scrollNext: () => void;
scrollPrev: () => void;
canScrollNext: boolean;
canScrollPrev: boolean;
handleKeyDown: (e: KeyboardEvent) => void;
options: CarouselOptions;
plugins: CarouselPlugins;
onInit: (e: CustomEvent<CarouselAPI>) => void;
scrollTo: (index: number, jump?: boolean) => void;
scrollSnaps: number[];
selectedIndex: number;
};
export function setEmblaContext(config: EmblaContext): EmblaContext {
setContext(EMBLA_CAROUSEL_CONTEXT, config);
return config;
setContext(EMBLA_CAROUSEL_CONTEXT, config);
return config;
}
export function getEmblaContext(name = 'This component') {
if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) {
throw new Error(`${name} must be used within a <Carousel.Root> component`);
}
return getContext<ReturnType<typeof setEmblaContext>>(EMBLA_CAROUSEL_CONTEXT);
if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) {
throw new Error(`${name} must be used within a <Carousel.Root> component`);
}
return getContext<ReturnType<typeof setEmblaContext>>(EMBLA_CAROUSEL_CONTEXT);
}

View File

@@ -5,15 +5,15 @@ import Previous from './carousel-previous.svelte';
import Next from './carousel-next.svelte';
export {
Root,
Content,
Item,
Previous,
Next,
//
Root as Carousel,
Content as CarouselContent,
Item as CarouselItem,
Previous as CarouselPrevious,
Next as CarouselNext
Root,
Content,
Item,
Previous,
Next,
//
Root as Carousel,
Content as CarouselContent,
Item as CarouselItem,
Previous as CarouselPrevious,
Next as CarouselNext
};

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import {Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import Check from '@lucide/svelte/icons/check';
import Minus from '@lucide/svelte/icons/minus';
import {cn} from '$lib/utils.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -14,21 +14,21 @@
</script>
<CheckboxPrimitive.Root
{...restProps}
bind:checked
bind:indeterminate
bind:ref
class={cn(
{...restProps}
bind:checked
bind:indeterminate
bind:ref
class={cn(
'peer box-content size-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50',
className
)}
>
{#snippet children({checked, indeterminate})}
{#snippet children({ checked, indeterminate })}
<span class="flex items-center justify-center text-current">
{#if indeterminate}
<Minus class="size-4"/>
<Minus class="size-4" />
{:else}
<Check class={cn('size-4', !checked && 'text-transparent')}/>
<Check class={cn('size-4', !checked && 'text-transparent')} />
{/if}
</span>
{/snippet}

View File

@@ -1,7 +1,7 @@
import Root from './checkbox.svelte';
export {
Root,
//
Root as Checkbox
Root,
//
Root as Checkbox
};

View File

@@ -1,15 +1,15 @@
import {Collapsible as CollapsiblePrimitive} from 'bits-ui';
import { Collapsible as CollapsiblePrimitive } from 'bits-ui';
const Root: typeof CollapsiblePrimitive.Root = CollapsiblePrimitive.Root;
const Trigger: typeof CollapsiblePrimitive.Trigger = CollapsiblePrimitive.Trigger;
const Content: typeof CollapsiblePrimitive.Content = CollapsiblePrimitive.Content;
export {
Root,
Content,
Trigger,
//
Root as Collapsible,
Content as CollapsibleContent,
Trigger as CollapsibleTrigger
Root,
Content,
Trigger,
//
Root as Collapsible,
Content as CollapsibleContent,
Trigger as CollapsibleTrigger
};

View File

@@ -1,38 +1,38 @@
<script lang="ts">
import {Dialog as DialogPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import X from '@lucide/svelte/icons/x';
import type {Snippet} from 'svelte';
import * as Dialog from './index.js';
import {cn} from '$lib/utils.js';
import { Dialog as DialogPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import X from '@lucide/svelte/icons/x';
import type { Snippet } from 'svelte';
import * as Dialog from './index.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
portalProps,
children,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: DialogPrimitive.PortalProps;
children: Snippet;
} = $props();
let {
ref = $bindable(null),
class: className,
portalProps,
children,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: DialogPrimitive.PortalProps;
children: Snippet;
} = $props();
</script>
<Dialog.Portal {...portalProps}>
<Dialog.Overlay/>
<DialogPrimitive.Content
bind:ref
class={cn(
<Dialog.Overlay />
<DialogPrimitive.Content
bind:ref
class={cn(
'fixed left-[50%] top-[50%] z-50 grid translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...restProps}
>
{@render children?.()}
<DialogPrimitive.Close
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X class="size-4"/>
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
{...restProps}
>
{@render children?.()}
<DialogPrimitive.Close
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X class="size-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {Dialog as DialogPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
</script>
<DialogPrimitive.Description
bind:ref
class={cn('text-sm text-muted-foreground', className)}
{...restProps}
bind:ref
class={cn('text-sm text-muted-foreground', className)}
{...restProps}
/>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import type {WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import {cn} from '$lib/utils.js';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...restProps}
bind:this={ref}
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...restProps}
>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import type {HTMLAttributes} from 'svelte/elements';
import type {WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import type { WithElementRef } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
{...restProps}
bind:this={ref}
class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
{...restProps}
>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import {Dialog as DialogPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
bind:ref
class={cn(
bind:ref
class={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...restProps}
{...restProps}
/>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {Dialog as DialogPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
</script>
<DialogPrimitive.Title
bind:ref
class={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...restProps}
bind:ref
class={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...restProps}
/>

View File

@@ -1,4 +1,4 @@
import {Dialog as DialogPrimitive} from 'bits-ui';
import { Dialog as DialogPrimitive } from 'bits-ui';
import Title from './dialog-title.svelte';
import Footer from './dialog-footer.svelte';
@@ -13,25 +13,25 @@ const Close: typeof DialogPrimitive.Close = DialogPrimitive.Close;
const Portal: typeof DialogPrimitive.Portal = DialogPrimitive.Portal;
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose
};

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import Check from '@lucide/svelte/icons/check';
import Minus from '@lucide/svelte/icons/minus';
import {cn} from '$lib/utils.js';
import type {Snippet} from 'svelte';
import { cn } from '$lib/utils.js';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
@@ -18,21 +18,21 @@
</script>
<DropdownMenuPrimitive.CheckboxItem
{...restProps}
bind:checked
bind:indeterminate
bind:ref
class={cn(
{...restProps}
bind:checked
bind:indeterminate
bind:ref
class={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
className
)}
>
{#snippet children({checked, indeterminate})}
{#snippet children({ checked, indeterminate })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<Minus class="size-4"/>
<Minus class="size-4" />
{:else}
<Check class={cn('size-4', !checked && 'text-transparent')}/>
<Check class={cn('size-4', !checked && 'text-transparent')} />
{/if}
</span>
{@render childrenProp?.()}

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import {cn} from '$lib/utils.js';
import {DropdownMenu as DropdownMenuPrimitive} from 'bits-ui';
import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
portalProps,
...restProps
}: DropdownMenuPrimitive.ContentProps & {
portalProps?: DropdownMenuPrimitive.PortalProps;
} = $props();
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
portalProps,
...restProps
}: DropdownMenuPrimitive.ContentProps & {
portalProps?: DropdownMenuPrimitive.PortalProps;
} = $props();
</script>
<DropdownMenuPrimitive.Portal {...portalProps}>
<DropdownMenuPrimitive.Content
bind:ref
{sideOffset}
class={cn(
<DropdownMenuPrimitive.Content
bind:ref
{sideOffset}
class={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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',
className
)}
{...restProps}
/>
{...restProps}
/>
</DropdownMenuPrimitive.Portal>

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: DropdownMenuPrimitive.GroupHeadingProps & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: DropdownMenuPrimitive.GroupHeadingProps & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.GroupHeading
bind:ref
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
bind:ref
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
/>

View File

@@ -1,23 +1,23 @@
<script lang="ts">
import {cn} from '$lib/utils.js';
import {DropdownMenu as DropdownMenuPrimitive} from 'bits-ui';
import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.Item
bind:ref
class={cn(
bind:ref
class={cn(
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
inset && 'pl-8',
className
)}
{...restProps}
{...restProps}
/>

View File

@@ -1,23 +1,23 @@
<script lang="ts">
import {cn} from '$lib/utils.js';
import {type WithElementRef} from 'bits-ui';
import type {HTMLAttributes} from 'svelte/elements';
import { cn } from '$lib/utils.js';
import { type WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean;
} = $props();
</script>
<div
bind:this={ref}
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
bind:this={ref}
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
>
{@render children?.()}
{@render children?.()}
</div>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChild} from 'bits-ui';
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from 'bits-ui';
import Circle from '@lucide/svelte/icons/circle';
import {cn} from '$lib/utils.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,19 +12,19 @@
</script>
<DropdownMenuPrimitive.RadioItem
bind:ref
class={cn(
bind:ref
class={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
className
)}
{...restProps}
{...restProps}
>
{#snippet children({checked})}
{#snippet children({ checked })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if checked}
<Circle class="size-2 fill-current"/>
<Circle class="size-2 fill-current" />
{/if}
</span>
{@render childrenProp?.({checked})}
{@render childrenProp?.({ checked })}
{/snippet}
</DropdownMenuPrimitive.RadioItem>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
</script>
<DropdownMenuPrimitive.Separator
bind:ref
class={cn('-mx-1 my-1 h-px bg-muted', className)}
{...restProps}
bind:ref
class={cn('-mx-1 my-1 h-px bg-muted', className)}
{...restProps}
/>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type {HTMLAttributes} from 'svelte/elements';
import {type WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import { type WithElementRef } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,9 +12,9 @@
</script>
<span
{...restProps}
bind:this={ref}
class={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...restProps}
bind:this={ref}
class={cn('ml-auto text-xs tracking-widest opacity-60', className)}
>
{@render children?.()}
</span>

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SubContentProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SubContentProps = $props();
</script>
<DropdownMenuPrimitive.SubContent
bind:ref
class={cn(
bind:ref
class={cn(
'z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none',
className
)}
{...restProps}
{...restProps}
/>

View File

@@ -1,28 +1,28 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChild} from 'bits-ui';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import {cn} from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from 'bits-ui';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithoutChild<DropdownMenuPrimitive.SubTriggerProps> & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithoutChild<DropdownMenuPrimitive.SubTriggerProps> & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.SubTrigger
bind:ref
class={cn(
bind:ref
class={cn(
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className
)}
{...restProps}
{...restProps}
>
{@render children?.()}
<ChevronRight class="ml-auto"/>
{@render children?.()}
<ChevronRight class="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>

View File

@@ -1,4 +1,4 @@
import {DropdownMenu as DropdownMenuPrimitive} from 'bits-ui';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
import Content from './dropdown-menu-content.svelte';
import GroupHeading from './dropdown-menu-group-heading.svelte';
@@ -17,34 +17,34 @@ const Group = DropdownMenuPrimitive.Group;
const RadioGroup = DropdownMenuPrimitive.RadioGroup;
export {
CheckboxItem,
Content,
Root as DropdownMenu,
CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent,
Group as DropdownMenuGroup,
GroupHeading as DropdownMenuGroupHeading,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
RadioGroup as DropdownMenuRadioGroup,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
Shortcut as DropdownMenuShortcut,
Sub as DropdownMenuSub,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
Trigger as DropdownMenuTrigger,
Group,
GroupHeading,
Item,
Label,
RadioGroup,
RadioItem,
Root,
Separator,
Shortcut,
Sub,
SubContent,
SubTrigger,
Trigger
CheckboxItem,
Content,
Root as DropdownMenu,
CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent,
Group as DropdownMenuGroup,
GroupHeading as DropdownMenuGroupHeading,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
RadioGroup as DropdownMenuRadioGroup,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
Shortcut as DropdownMenuShortcut,
Sub as DropdownMenuSub,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
Trigger as DropdownMenuTrigger,
Group,
GroupHeading,
Item,
Label,
RadioGroup,
RadioItem,
Root,
Separator,
Shortcut,
Sub,
SubContent,
SubTrigger,
Trigger
};

View File

@@ -1,7 +1,7 @@
import Root from './input.svelte';
export {
Root,
//
Root as Input
Root,
//
Root as Input
};

View File

@@ -1,46 +1,46 @@
<script lang="ts">
import type {HTMLInputAttributes, HTMLInputTypeAttribute} from 'svelte/elements';
import type {WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import type { WithElementRef } from 'bits-ui';
import { cn } from '$lib/utils.js';
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
type Props = WithElementRef<
Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
>;
type Props = WithElementRef<
Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
>;
let {
ref = $bindable(null),
value = $bindable(),
type,
files = $bindable(),
class: className,
...restProps
}: Props = $props();
let {
ref = $bindable(null),
value = $bindable(),
type,
files = $bindable(),
class: className,
...restProps
}: Props = $props();
</script>
{#if type === 'file'}
<input
bind:this={ref}
class={cn(
<input
bind:this={ref}
class={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className
)}
type="file"
bind:files
bind:value
{...restProps}
/>
type="file"
bind:files
bind:value
{...restProps}
/>
{:else}
<input
bind:this={ref}
class={cn(
<input
bind:this={ref}
class={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className
)}
{type}
bind:value
{...restProps}
/>
{type}
bind:value
{...restProps}
/>
{/if}

View File

@@ -1,7 +1,7 @@
import Root from './label.svelte';
export {
Root,
//
Root as Label
Root,
//
Root as Label
};

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import {Label as LabelPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Label as LabelPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: LabelPrimitive.RootProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: LabelPrimitive.RootProps = $props();
</script>
<LabelPrimitive.Root
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
className
)}

View File

@@ -1,4 +1,4 @@
import {Menubar as MenubarPrimitive} from 'bits-ui';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import Root from './menubar.svelte';
import CheckboxItem from './menubar-checkbox-item.svelte';
import Content from './menubar-content.svelte';
@@ -17,35 +17,35 @@ const Sub = MenubarPrimitive.Sub;
const RadioGroup = MenubarPrimitive.RadioGroup;
export {
Root,
CheckboxItem,
Content,
Item,
GroupHeading,
RadioItem,
Separator,
Shortcut,
SubContent,
SubTrigger,
Trigger,
Menu,
Group,
Sub,
RadioGroup,
//
Root as Menubar,
CheckboxItem as MenubarCheckboxItem,
Content as MenubarContent,
Item as MenubarItem,
GroupHeading as MenubarGroupHeading,
RadioItem as MenubarRadioItem,
Separator as MenubarSeparator,
Shortcut as MenubarShortcut,
SubContent as MenubarSubContent,
SubTrigger as MenubarSubTrigger,
Trigger as MenubarTrigger,
Menu as MenubarMenu,
Group as MenubarGroup,
Sub as MenubarSub,
RadioGroup as MenubarRadioGroup
Root,
CheckboxItem,
Content,
Item,
GroupHeading,
RadioItem,
Separator,
Shortcut,
SubContent,
SubTrigger,
Trigger,
Menu,
Group,
Sub,
RadioGroup,
//
Root as Menubar,
CheckboxItem as MenubarCheckboxItem,
Content as MenubarContent,
Item as MenubarItem,
GroupHeading as MenubarGroupHeading,
RadioItem as MenubarRadioItem,
Separator as MenubarSeparator,
Shortcut as MenubarShortcut,
SubContent as MenubarSubContent,
SubTrigger as MenubarSubTrigger,
Trigger as MenubarTrigger,
Menu as MenubarMenu,
Group as MenubarGroup,
Sub as MenubarSub,
RadioGroup as MenubarRadioGroup
};

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import {Menubar as MenubarPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import { Menubar as MenubarPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import Check from '@lucide/svelte/icons/check';
import Minus from '@lucide/svelte/icons/minus';
import {cn} from '$lib/utils.js';
import type {Snippet} from 'svelte';
import { cn } from '$lib/utils.js';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
@@ -18,21 +18,21 @@
</script>
<MenubarPrimitive.CheckboxItem
{...restProps}
bind:checked
bind:indeterminate
bind:ref
class={cn(
{...restProps}
bind:checked
bind:indeterminate
bind:ref
class={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
className
)}
>
{#snippet children({checked, indeterminate})}
{#snippet children({ checked, indeterminate })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<Minus class="size-4"/>
<Minus class="size-4" />
{:else}
<Check class={cn('size-4', !checked && 'text-transparent')}/>
<Check class={cn('size-4', !checked && 'text-transparent')} />
{/if}
</span>
{@render childrenProp?.()}

View File

@@ -1,32 +1,32 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
sideOffset = 8,
alignOffset = -4,
align = 'start',
side = 'bottom',
portalProps,
...restProps
}: MenubarPrimitive.ContentProps & {
portalProps?: MenubarPrimitive.PortalProps;
} = $props();
let {
ref = $bindable(null),
class: className,
sideOffset = 8,
alignOffset = -4,
align = 'start',
side = 'bottom',
portalProps,
...restProps
}: MenubarPrimitive.ContentProps & {
portalProps?: MenubarPrimitive.PortalProps;
} = $props();
</script>
<MenubarPrimitive.Portal {...portalProps}>
<MenubarPrimitive.Content
{...restProps}
{align}
{alignOffset}
bind:ref
class={cn(
<MenubarPrimitive.Content
{...restProps}
{align}
{alignOffset}
bind:ref
class={cn(
'z-50 min-w-[12rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none',
className
)}
{side}
{sideOffset}
/>
{side}
{sideOffset}
/>
</MenubarPrimitive.Portal>

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: MenubarPrimitive.GroupHeadingProps & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: MenubarPrimitive.GroupHeadingProps & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.GroupHeading
{...restProps}
bind:ref
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
bind:ref
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
/>

View File

@@ -1,21 +1,21 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: MenubarPrimitive.ItemProps & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: MenubarPrimitive.ItemProps & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.Item
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
inset && 'pl-8',
className

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import {Menubar as MenubarPrimitive, type WithoutChild} from 'bits-ui';
import { Menubar as MenubarPrimitive, type WithoutChild } from 'bits-ui';
import Circle from '@lucide/svelte/icons/circle';
import {cn} from '$lib/utils.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,19 +12,19 @@
</script>
<MenubarPrimitive.RadioItem
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
className
)}
>
{#snippet children({checked})}
{#snippet children({ checked })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if checked}
<Circle class="size-2 fill-current"/>
<Circle class="size-2 fill-current" />
{/if}
</span>
{@render childrenProp?.({checked})}
{@render childrenProp?.({ checked })}
{/snippet}
</MenubarPrimitive.RadioItem>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.SeparatorProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.SeparatorProps = $props();
</script>
<MenubarPrimitive.Separator
{...restProps}
bind:ref
class={cn('-mx-1 my-1 h-px bg-muted', className)}
{...restProps}
bind:ref
class={cn('-mx-1 my-1 h-px bg-muted', className)}
/>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type {HTMLAttributes} from 'svelte/elements';
import type {WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import type { WithElementRef } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,9 +12,9 @@
</script>
<span
{...restProps}
bind:this={ref}
class={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
{...restProps}
bind:this={ref}
class={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
>
{@render children?.()}
</span>

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.SubContentProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.SubContentProps = $props();
</script>
<MenubarPrimitive.SubContent
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'z-50 min-w-max rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none',
className
)}

View File

@@ -1,28 +1,28 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: MenubarPrimitive.SubTriggerProps & {
inset?: boolean;
} = $props();
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: MenubarPrimitive.SubTriggerProps & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.SubTrigger
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
>
{@render children?.()}
<ChevronRight class="ml-auto size-4"/>
{@render children?.()}
<ChevronRight class="ml-auto size-4" />
</MenubarPrimitive.SubTrigger>

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.TriggerProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.TriggerProps = $props();
</script>
<MenubarPrimitive.Trigger
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
className
)}

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import {Menubar as MenubarPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.RootProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.RootProps = $props();
</script>
<MenubarPrimitive.Root
{...restProps}
bind:ref
class={cn(
{...restProps}
bind:ref
class={cn(
'flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm',
className
)}

View File

@@ -1,7 +1,7 @@
import Root from './progress.svelte';
export {
Root,
//
Root as Progress
Root,
//
Root as Progress
};

View File

@@ -1,24 +1,24 @@
<script lang="ts">
import {Progress as ProgressPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Progress as ProgressPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
max = 100,
value,
...restProps
}: WithoutChildrenOrChild<ProgressPrimitive.RootProps> = $props();
let {
ref = $bindable(null),
class: className,
max = 100,
value,
...restProps
}: WithoutChildrenOrChild<ProgressPrimitive.RootProps> = $props();
</script>
<ProgressPrimitive.Root
{...restProps}
bind:ref
class={cn('relative h-2 w-full overflow-hidden rounded-full bg-primary/20', className)}
{value}
{...restProps}
bind:ref
class={cn('relative h-2 w-full overflow-hidden rounded-full bg-primary/20', className)}
{value}
>
<div
class="h-full w-full flex-1 bg-primary transition-all"
style={`transform: translateX(-${100 - (100 * (value ?? 0)) / (max ?? 1)}%)`}
></div>
<div
class="h-full w-full flex-1 bg-primary transition-all"
style={`transform: translateX(-${100 - (100 * (value ?? 0)) / (max ?? 1)}%)`}
></div>
</ProgressPrimitive.Root>

View File

@@ -2,9 +2,9 @@ import Root from './radio-group.svelte';
import Item from './radio-group-item.svelte';
export {
Root,
Item,
//
Root as RadioGroup,
Item as RadioGroupItem
Root,
Item,
//
Root as RadioGroup,
Item as RadioGroupItem
};

View File

@@ -1,30 +1,30 @@
<script lang="ts">
import {RadioGroup as RadioGroupPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import Circle from '@lucide/svelte/icons/circle';
import {cn} from '$lib/utils.js';
import { RadioGroup as RadioGroupPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import Circle from '@lucide/svelte/icons/circle';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> & {
value: string;
} = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> & {
value: string;
} = $props();
</script>
<RadioGroupPrimitive.Item
bind:ref
class={cn(
bind:ref
class={cn(
'aspect-square size-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...restProps}
{...restProps}
>
{#snippet children({checked})}
<div class="flex items-center justify-center">
{#if checked}
<Circle class="size-3.5 fill-primary"/>
{/if}
</div>
{/snippet}
{#snippet children({ checked })}
<div class="flex items-center justify-center">
{#if checked}
<Circle class="size-3.5 fill-primary" />
{/if}
</div>
{/snippet}
</RadioGroupPrimitive.Item>

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import {RadioGroup as RadioGroupPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
value = $bindable(),
...restProps
}: RadioGroupPrimitive.RootProps = $props();
let {
ref = $bindable(null),
class: className,
value = $bindable(),
...restProps
}: RadioGroupPrimitive.RootProps = $props();
</script>
<RadioGroupPrimitive.Root bind:value class={cn('grid gap-2', className)} {...restProps} bind:ref/>
<RadioGroupPrimitive.Root bind:value class={cn('grid gap-2', className)} {...restProps} bind:ref />

View File

@@ -1,4 +1,4 @@
import {Select as SelectPrimitive} from 'bits-ui';
import { Select as SelectPrimitive } from 'bits-ui';
import GroupHeading from './select-group-heading.svelte';
import Item from './select-item.svelte';
@@ -12,23 +12,23 @@ const Root = SelectPrimitive.Root;
const Group = SelectPrimitive.Group;
export {
Root,
Item,
Group,
GroupHeading,
Content,
Trigger,
Separator,
ScrollDownButton,
ScrollUpButton,
//
Root as Select,
Item as SelectItem,
Group as SelectGroup,
GroupHeading as SelectGroupHeading,
Content as SelectContent,
Trigger as SelectTrigger,
Separator as SelectSeparator,
ScrollDownButton as SelectScrollDownButton,
ScrollUpButton as SelectScrollUpButton
Root,
Item,
Group,
GroupHeading,
Content,
Trigger,
Separator,
ScrollDownButton,
ScrollUpButton,
//
Root as Select,
Item as SelectItem,
Group as SelectGroup,
GroupHeading as SelectGroupHeading,
Content as SelectContent,
Trigger as SelectTrigger,
Separator as SelectSeparator,
ScrollDownButton as SelectScrollDownButton,
ScrollUpButton as SelectScrollUpButton
};

View File

@@ -1,38 +1,38 @@
<script lang="ts">
import {Select as SelectPrimitive, type WithoutChild} from 'bits-ui';
import * as Select from './index.js';
import {cn} from '$lib/utils.js';
import { Select as SelectPrimitive, type WithoutChild } from 'bits-ui';
import * as Select from './index.js';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
portalProps,
children,
...restProps
}: WithoutChild<SelectPrimitive.ContentProps> & {
portalProps?: SelectPrimitive.PortalProps;
} = $props();
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
portalProps,
children,
...restProps
}: WithoutChild<SelectPrimitive.ContentProps> & {
portalProps?: SelectPrimitive.PortalProps;
} = $props();
</script>
<SelectPrimitive.Portal {...portalProps}>
<SelectPrimitive.Content
bind:ref
{sideOffset}
class={cn(
<SelectPrimitive.Content
bind:ref
{sideOffset}
class={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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',
className
)}
{...restProps}
>
<Select.ScrollUpButton/>
<SelectPrimitive.Viewport
class={cn(
{...restProps}
>
<Select.ScrollUpButton />
<SelectPrimitive.Viewport
class={cn(
'h-[var(--bits-select-anchor-height)] w-full min-w-[var(--bits-select-anchor-width)] p-1'
)}
>
{@render children?.()}
</SelectPrimitive.Viewport>
<Select.ScrollDownButton/>
</SelectPrimitive.Content>
>
{@render children?.()}
</SelectPrimitive.Viewport>
<Select.ScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import {Select as SelectPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
import { Select as SelectPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SelectPrimitive.GroupHeadingProps = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: SelectPrimitive.GroupHeadingProps = $props();
</script>
<SelectPrimitive.GroupHeading
bind:ref
class={cn('px-2 py-1.5 text-sm font-semibold', className)}
{...restProps}
bind:ref
class={cn('px-2 py-1.5 text-sm font-semibold', className)}
{...restProps}
/>

View File

@@ -1,37 +1,37 @@
<script lang="ts">
import {Select as SelectPrimitive, type WithoutChild} from 'bits-ui';
import Check from '@lucide/svelte/icons/check';
import {cn} from '$lib/utils.js';
import { Select as SelectPrimitive, type WithoutChild } from 'bits-ui';
import Check from '@lucide/svelte/icons/check';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
value,
label,
children: childrenProp,
...restProps
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
let {
ref = $bindable(null),
class: className,
value,
label,
children: childrenProp,
...restProps
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
</script>
<SelectPrimitive.Item
bind:ref
{value}
class={cn(
bind:ref
{value}
class={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
className
)}
{...restProps}
{...restProps}
>
{#snippet children({selected, highlighted})}
{#snippet children({ selected, highlighted })}
<span class="absolute right-2 flex size-3.5 items-center justify-center">
{#if selected}
<Check class="size-4"/>
<Check class="size-4" />
{/if}
</span>
{#if childrenProp}
{@render childrenProp({selected, highlighted})}
{:else}
{label || value}
{/if}
{/snippet}
{#if childrenProp}
{@render childrenProp({ selected, highlighted })}
{:else}
{label || value}
{/if}
{/snippet}
</SelectPrimitive.Item>

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import {Select as SelectPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import {cn} from '$lib/utils.js';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import { Select as SelectPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
</script>
<SelectPrimitive.ScrollDownButton
bind:ref
class={cn('flex cursor-default items-center justify-center py-1', className)}
{...restProps}
bind:ref
class={cn('flex cursor-default items-center justify-center py-1', className)}
{...restProps}
>
<ChevronDown class="size-4"/>
<ChevronDown class="size-4" />
</SelectPrimitive.ScrollDownButton>

Some files were not shown because too many files have changed in this diff Show More