mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-21 00:05:36 +02:00
turns out intellij formats the code before committing which causes the ci/cd checks to fail
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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},
|
||||
{/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},
|
||||
{/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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from './checkbox.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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?.()}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from './input.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input
|
||||
Root,
|
||||
//
|
||||
Root as Input
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from './label.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label
|
||||
Root,
|
||||
//
|
||||
Root as Label
|
||||
};
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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?.()}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from './progress.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Progress
|
||||
Root,
|
||||
//
|
||||
Root as Progress
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user