working on the frontend, adding torrent page, working on show directory, working on the api

This commit is contained in:
maxDorninger
2025-05-11 10:59:38 +02:00
parent 180771882d
commit ef7b020043
164 changed files with 5389 additions and 1594 deletions

24
.gitignore vendored
View File

@@ -11,3 +11,27 @@ tv/*
log.txt
res/*
/backend/src/indexer/indexers/prowlarr.http
/web/cache/
web/node_modules
# Output
web/.output
web/.vercel
web/.netlify
web/.wrangler
web/.svelte-kit
web/build
# OS
web/.DS_Store
web/Thumbs.db
# Env
web/.env
web/.env.*
web/!.env.example
web/!.env.test
# Vite
web/vite.config.js.timestamp-*
web/vite.config.ts.timestamp-*

View File

@@ -47,7 +47,7 @@ class Prowlarr(GenericIndexer):
title=result['sortTitle'],
seeders=result['seeders'],
flags=result['indexerFlags'],
)
size=result['size'], )
)
return result_list
else:

View File

@@ -16,3 +16,4 @@ class IndexerQueryResult(Base):
flags = mapped_column(ARRAY(String))
quality: Mapped[Quality]
season = mapped_column(ARRAY(Integer))
size = Mapped[int]

View File

@@ -19,6 +19,7 @@ class IndexerQueryResult(BaseModel):
download_url: str
seeders: int
flags: list[str]
size: int
@computed_field(return_type=Quality)
@property
@@ -64,18 +65,6 @@ class IndexerQueryResult(BaseModel):
return self.quality.value < other.quality.value
return self.seeders > other.seeders
# def download(self) -> SeasonTorrent:
# """
# downloads a torrent file and returns the filepath
# """
# import requests
# url = self.download_url
# torrent_filepath = self.title + ".torrent"
# with open(torrent_filepath, 'wb') as out_file:
# content = requests.get(url).content
# out_file.write(content)
# return SeasonTorrent(status=None, title=self.title, quality=self.quality, id=self.id)
class PublicIndexerQueryResult(BaseModel):
title: str
@@ -84,3 +73,4 @@ class PublicIndexerQueryResult(BaseModel):
seeders: int
flags: list[str]
season: list[int]
size: int

View File

@@ -11,10 +11,11 @@ def search(query: str, db: Session) -> list[IndexerQueryResult]:
log.debug(f"Searching for Torrent: {query}")
for indexer in indexers:
results.extend(indexer.get_search_results(query))
for i in indexers:
results.extend(i.get_search_results(query))
for result in results:
save_result(result=result, db=db)
log.debug(f"Found Torrents: {results}")
return results

View File

@@ -55,6 +55,7 @@ from auth.users import oauth_client
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
import tv.router
import torrent.router
@@ -75,8 +76,7 @@ app = FastAPI(root_path="/api/v1")
if basic_config.DEVELOPMENT:
origins = [
"http://localhost:5173",
"http://127.0.0.1:5173",
"*",
]
app.add_middleware(
@@ -153,5 +153,9 @@ app.include_router(
prefix="/torrent",
tags=["torrent"]
)
# static file routers
app.mount("/static/image", StaticFiles(directory=basic_config.image_directory), name="static-images")
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=5049, log_config=LOGGING_CONFIG)

View File

@@ -2,6 +2,7 @@ import logging
import metadataProvider.tmdb
from metadataProvider.abstractMetaDataProvider import metadata_providers
from metadataProvider.schemas import MetaDataProviderShowSearchResult
from tv.schemas import Show
log = logging.getLogger(__name__)
@@ -13,5 +14,5 @@ def get_show_metadata(id: int = None, provider: str = "tmdb") -> Show:
return metadata_providers[provider].get_show_metadata(id)
def search_show(query: str, provider: str = "tmdb"):
def search_show(query: str, provider: str = "tmdb") -> list[MetaDataProviderShowSearchResult]:
return metadata_providers[provider].search_show(query)

View File

@@ -2,6 +2,7 @@ import logging
from abc import ABC, abstractmethod
import config
from metadataProvider.schemas import MetaDataProviderShowSearchResult
from tv.schemas import Show
log = logging.getLogger(__name__)
@@ -19,7 +20,7 @@ class AbstractMetadataProvider(ABC):
pass
@abstractmethod
def search_show(self, query):
def search_show(self, query) -> list[MetaDataProviderShowSearchResult]:
pass

View File

@@ -0,0 +1,11 @@
from pydantic import BaseModel
class MetaDataProviderShowSearchResult(BaseModel):
poster_path: str | None
overview: str | None
name: str
external_id: int
year: int | None
metadata_provider: str
added: bool

View File

@@ -6,7 +6,9 @@ import tmdbsimple
from pydantic_settings import BaseSettings
from tmdbsimple import TV, TV_Seasons
import metadataProvider.utils
from metadataProvider.abstractMetaDataProvider import AbstractMetadataProvider, register_metadata_provider
from metadataProvider.schemas import MetaDataProviderShowSearchResult
from tv.schemas import Episode, Season, Show, SeasonNumber, EpisodeNumber
@@ -56,11 +58,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
)
)
year: str | None = show_metadata["first_air_date"]
if year:
year: int = int(year.split('-')[0])
else:
year = None
year = metadataProvider.utils.get_year_from_first_air_date(show_metadata["first_air_date"])
show = Show(
external_id=id,
@@ -71,23 +69,42 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
metadata_provider=self.name,
)
# TODO: convert images automatically to .jpg
# downloading the poster
poster_url = "https://image.tmdb.org/t/p/original" + show_metadata["poster_path"]
res = requests.get(poster_url, stream=True)
content_type = res.headers["content-type"]
file_extension = mimetypes.guess_extension(content_type)
if res.status_code == 200:
with open(self.storage_path.joinpath(str(show.id) + file_extension), 'wb') as f:
f.write(res.content)
log.info(f"image for show {show.name} successfully downloaded")
if show_metadata["poster_path"] is not None:
poster_url = "https://image.tmdb.org/t/p/original" + show_metadata["poster_path"]
res = requests.get(poster_url, stream=True)
content_type = res.headers["content-type"]
file_extension = mimetypes.guess_extension(content_type)
if res.status_code == 200:
with open(self.storage_path.joinpath(str(show.id) + file_extension), 'wb') as f:
f.write(res.content)
log.info(f"image for show {show.name} successfully downloaded")
else:
log.warning(f"image for show {show.name} could not be downloaded")
return show
def search_show(self, query: str):
return tmdbsimple.Search().tv(query=query)
def search_show(self, query: str) -> list[MetaDataProviderShowSearchResult]:
results = tmdbsimple.Search().tv(query=query)
formatted_results = []
for result in results["results"]:
if result["poster_path"] is not None:
poster_url = "https://image.tmdb.org/t/p/original" + result["poster_path"]
else:
poster_url = None
formatted_results.append(
MetaDataProviderShowSearchResult(
poster_path=poster_url,
overview=result["overview"],
name=result["name"],
external_id=result["id"],
year=metadataProvider.utils.get_year_from_first_air_date(result["first_air_date"]),
metadata_provider=self.name,
added=False,
)
)
return formatted_results
def __init__(self, api_key: str = None):
tmdbsimple.API_KEY = api_key

View File

@@ -0,0 +1,5 @@
def get_year_from_first_air_date(first_air_date: str | None) -> int | None:
if first_air_date:
return int(first_air_date.split('-')[0])
else:
return None

View File

@@ -6,6 +6,8 @@ import tv.service
from auth.users import current_active_user, current_superuser
from database import DbSessionDependency
from indexer.schemas import PublicIndexerQueryResult, IndexerQueryResultId
from metadataProvider.schemas import MetaDataProviderShowSearchResult
from torrent.schemas import Torrent
from tv.exceptions import MediaAlreadyExists
from tv.schemas import Show, SeasonRequest, ShowId
@@ -40,10 +42,12 @@ def delete_a_show(db: DbSessionDependency, show_id: ShowId):
# --------------------------------
@router.get("/shows", dependencies=[Depends(current_active_user)], response_model=list[Show])
def get_all_shows(db: DbSessionDependency):
def get_all_shows(db: DbSessionDependency, external_id: int = None, metadata_provider: str = "tmdb"):
""""""
return tv.service.get_all_shows(db=db)
if external_id is not None:
return tv.service.get_show_by_external_id(db=db, external_id=external_id, metadata_provider=metadata_provider)
else:
return tv.service.get_all_shows(db=db)
@router.get("/shows/{show_id}", dependencies=[Depends(current_active_user)], response_model=Show)
def get_a_show(db: DbSessionDependency, show_id: ShowId):
@@ -94,7 +98,8 @@ def get_torrents_for_a_season(db: DbSessionDependency, show_id: ShowId, season_n
# download a torrent
@router.post("/torrents", status_code=status.HTTP_200_OK, dependencies=[Depends(current_superuser)])
@router.post("/torrents", status_code=status.HTTP_200_OK, response_model=Torrent,
dependencies=[Depends(current_superuser)])
def download_a_torrent(db: DbSessionDependency, public_indexer_result_id: IndexerQueryResultId, show_id: ShowId,
override_file_path_suffix: str = ""):
return tv.service.download_torrent(db=db, public_indexer_result_id=public_indexer_result_id, show_id=show_id,
@@ -104,6 +109,7 @@ def download_a_torrent(db: DbSessionDependency, public_indexer_result_id: Indexe
# SEARCH SHOWS ON METADATA PROVIDERS
# --------------------------------
@router.get("/search", dependencies=[Depends(current_active_user)])
def search_metadata_providers_for_a_show(query: str, metadata_provider: str = "tmdb"):
return metadataProvider.search_show(query, metadata_provider)
@router.get("/search", dependencies=[Depends(current_active_user)],
response_model=list[MetaDataProviderShowSearchResult])
def search_metadata_providers_for_a_show(db: DbSessionDependency, query: str, metadata_provider: str = "tmdb"):
return tv.service.search_for_show(query=query, metadata_provider=metadata_provider, db=db)

View File

@@ -45,7 +45,7 @@ class Show(BaseModel):
name: str
overview: str
year: int
year: int | None
external_id: int
metadata_provider: str

View File

@@ -5,6 +5,7 @@ import metadataProvider
import tv.repository
from indexer import IndexerQueryResult
from indexer.schemas import IndexerQueryResultId
from metadataProvider.schemas import MetaDataProviderShowSearchResult
from torrent.schemas import Torrent
from torrent.service import TorrentService
from tv import log
@@ -53,11 +54,15 @@ def get_all_available_torrents_for_a_season(db: Session, season_number: int, sho
IndexerQueryResult]:
log.debug(f"getting all available torrents for season {season_number} for show {show_id}")
show = tv.repository.get_show(show_id=show_id, db=db)
if search_query_override is not None:
if search_query_override:
search_query = search_query_override
else:
search_query = show.name + " Season " + str(season_number)
# TODO: add more Search query strings and combine all the results, like "season 3", "s03", "s3"
search_query = show.name + " s" + str(season_number).zfill(2)
torrents: list[IndexerQueryResult] = indexer.service.search(query=search_query, db=db)
if search_query_override:
log.debug(f"Found with search query override {torrents.__len__()} torrents: {torrents}")
return torrents
result: list[IndexerQueryResult] = []
for torrent in torrents:
if season_number in torrent.season:
@@ -70,10 +75,20 @@ def get_all_shows(db: Session) -> list[Show]:
return tv.repository.get_shows(db=db)
def search_for_show(query: str, metadata_provider: str, db: Session) -> list[MetaDataProviderShowSearchResult]:
results = metadataProvider.search_show(query, metadata_provider)
for result in results:
if check_if_show_exists(db=db, external_id=result.external_id, metadata_provider=metadata_provider):
result.added = True
return results
def get_show_by_id(db: Session, show_id: ShowId) -> Show | None:
return tv.repository.get_show(show_id=show_id, db=db)
def get_show_by_external_id(db: Session, external_id: int, metadata_provider: str) -> Show | None:
return tv.repository.get_show_by_external_id(external_id=external_id, metadata_provider=metadata_provider, db=db)
def get_season(db: Session, season_id: SeasonId) -> Season:
return tv.repository.get_season(season_id=season_id, db=db)

View File

@@ -20,6 +20,7 @@ services:
- TZ=Etc/UTC
volumes:
- .\res\prowlarr:/config
restart: unless-stopped
ports:
- "9696:9696"
qbittorrent:

23
web/.gitignore vendored
View File

@@ -1,23 +0,0 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -1,18 +1,18 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

View File

@@ -5,10 +5,35 @@ import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import {fileURLToPath} from 'node:url';
import ts from 'typescript-eslint';
import unusedImports from 'eslint-plugin-unused-imports';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default ts.config(
{
plugins: {
'unused-imports': unusedImports
},
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_'
}
],
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
]
}
},
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,

2472
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,57 @@
{
"name": "web",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "eslint . && prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@fontsource/fira-mono": "^5.0.0",
"@lucide/svelte": "^0.503.0",
"@neoconfetti/svelte": "^2.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.3.19",
"clsx": "^2.1.1",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.10",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwind-merge": "^3.2.0",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^6.0.0"
}
"name": "web",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "eslint --fix . && prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@fontsource/fira-mono": "^5.0.0",
"@lucide/svelte": "^0.503.0",
"@neoconfetti/svelte": "^2.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.4.6",
"clsx": "^2.1.1",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.10",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwind-merge": "^3.2.0",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^6.0.0"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.806.0",
"@sveltejs/adapter-node": "^5.2.12",
"eslint-plugin-unused-imports": "^4.1.4",
"lucide-svelte": "^0.507.0",
"sharp": "^0.34.1",
"sveltekit-image-optimize": "^0.0.7",
"uuid": "^11.1.0"
}
}

View File

@@ -73,4 +73,4 @@
body {
@apply bg-background text-foreground;
}
}
}

7
web/src/app.d.ts vendored
View File

@@ -7,13 +7,6 @@ declare global {
// interface PageData {}
// interface PageState {}
// interface Platform {}
interface User {
id: string;
email: string;
is_active?: boolean;
is_superuser?: boolean;
is_verified?: boolean;
}
}
}

12
web/src/hooks.server.ts Normal file
View File

@@ -0,0 +1,12 @@
import {createImageOptimizer} from 'sveltekit-image-optimize';
import type {Handle} from '@sveltejs/kit';
import {createFileSystemCache} from 'sveltekit-image-optimize/cache-adapters';
const cache = createFileSystemCache('./cache');
const imageHandler = createImageOptimizer({
cache: cache,
fallbackFormat: 'avif',
quality: 20
});
export const handle: Handle = imageHandler;

View File

@@ -1,122 +1,107 @@
<script lang="ts" module>
import BookOpen from "@lucide/svelte/icons/book-open";
import Bot from "@lucide/svelte/icons/bot";
import Settings2 from "@lucide/svelte/icons/settings-2";
import SquareTerminal from "@lucide/svelte/icons/square-terminal";
import LifeBuoy from '@lucide/svelte/icons/life-buoy';
import Send from '@lucide/svelte/icons/send';
import TvIcon from '@lucide/svelte/icons/tv';
import LayoutPanelLeft from '@lucide/svelte/icons/layout-panel-left';
import DownloadIcon from '@lucide/svelte/icons/download';
const data = {
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
{
title: "Models",
url: "#",
icon: Bot,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
};
const data = {
navMain: [
{
title: 'TV',
url: '#',
icon: TvIcon,
isActive: true,
items: [
{
title: 'Shows',
url: '/dashboard/tv'
},
{
title: 'Add Show',
url: '/dashboard/tv/add-show'
},
{
title: 'Settings',
url: '#'
}
]
},
{
title: 'Torrents',
url: '#',
icon: DownloadIcon,
isActive: true,
items: [
{
title: 'Show Torrents',
url: '/dashboard/torrents'
}
]
}
],
navSecondary: [
{
title: 'Support',
url: '#',
icon: LifeBuoy
},
{
title: 'Feedback',
url: '#',
icon: Send
}
],
projects: [
{
name: 'Dashboard',
url: '/dashboard',
icon: LayoutPanelLeft
}
]
};
</script>
<script lang="ts">
import NavMain from "$lib/components/nav-main.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 NavMain from '$lib/components/nav-main.svelte';
import NavProjects from '$lib/components/nav-projects.svelte';
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 Command from '@lucide/svelte/icons/command';
import type {ComponentProps} from 'svelte';
let {
ref = $bindable(null),
collapsible = "icon",
...restProps
}: ComponentProps<typeof Sidebar.Root> = $props();
let {ref = $bindable(null), ...restProps}: ComponentProps<typeof Sidebar.Root> = $props();
</script>
<Sidebar.Root {...restProps} bind:ref {collapsible}>
<Sidebar.Header>
<h1 class="font-bold">MediaManager</h1>
</Sidebar.Header>
<Sidebar.Content>
<NavMain items={data.navMain}/>
</Sidebar.Content>
<Sidebar.Footer>
<NavUser user={data.user}/>
</Sidebar.Footer>
<Sidebar.Rail/>
<Sidebar.Root bind:ref variant="inset" {...restProps}>
<Sidebar.Header>
<Sidebar.Menu>
<Sidebar.MenuItem>
<Sidebar.MenuButton size="lg">
{#snippet child({props})}
<a href="##" {...props}>
<div
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
>
<Command class="size-4"/>
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Acme Inc</span>
<span class="truncate text-xs">Enterprise</span>
</div>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
</Sidebar.Menu>
</Sidebar.Header>
<Sidebar.Content>
<NavMain items={data.navMain}/>
<NavProjects projects={data.projects}/>
<NavSecondary items={data.navSecondary} class="mt-auto"/>
</Sidebar.Content>
<Sidebar.Footer>
<NavUser/>
</Sidebar.Footer>
</Sidebar.Root>

View File

@@ -0,0 +1,67 @@
<script lang="ts">
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;
}>();
// 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'
};
// 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';
</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}
</div>

View File

@@ -1,102 +1,99 @@
<script lang="ts">
import {Button} from "$lib/components/ui/button/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 {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';
let apiUrl = env.PUBLIC_API_URL
let apiUrl = env.PUBLIC_API_URL;
let email = '';
let password = '';
let errorMessage = '';
let isLoading = false;
let email = '';
let password = '';
let errorMessage = '';
let isLoading = false;
async function handleLogin(event: Event) {
event.preventDefault();
async function handleLogin(event: Event) {
event.preventDefault();
isLoading = true;
errorMessage = '';
isLoading = true;
errorMessage = '';
const formData = new URLSearchParams();
formData.append('username', email);
formData.append('password', password);
console.log(apiUrl + '/auth/cookie/login')
try {
const response = await fetch(apiUrl + '/auth/cookie/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.toString(),
credentials: 'include',
});
const formData = new URLSearchParams();
formData.append('username', email);
formData.append('password', password);
console.log(apiUrl + '/auth/cookie/login');
try {
const response = await fetch(apiUrl + '/auth/cookie/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString(),
credentials: 'include'
});
if (response.ok) {
console.log('Login successful!');
console.log("Received User Data: ", response);
await goto('/dashboard');
errorMessage = 'Login successful! Redirecting...';
} else {
let errorText = await response.text();
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || 'Login failed. Please check your credentials.';
} catch {
errorMessage = errorText || 'Login failed. Please check your credentials.';
}
console.error('Login failed:', response.status, errorText);
}
} catch (error) {
console.error('Login request failed:', error);
errorMessage = 'An error occurred during the login request.';
} finally {
isLoading = false;
}
}
if (response.ok) {
console.log('Login successful!');
console.log('Received User Data: ', response);
await goto('/dashboard');
errorMessage = 'Login successful! Redirecting...';
} else {
let errorText = await response.text();
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || 'Login failed. Please check your credentials.';
} catch {
errorMessage = errorText || 'Login failed. Please check your credentials.';
}
console.error('Login failed:', response.status, errorText);
}
} catch (error) {
console.error('Login request failed:', error);
errorMessage = 'An error occurred during the login request.';
} finally {
isLoading = false;
}
}
</script>
<Card.Root class="mx-auto max-w-sm">
<Card.Header>
<Card.Title class="text-2xl">Login</Card.Title>
<Card.Description>Enter your email below to login to your account</Card.Description>
</Card.Header>
<Card.Content>
<form class="grid gap-4" on:submit={handleLogin}>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input bind:value={email} id="email" placeholder="m@example.com" required type="email"/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a class="ml-auto inline-block text-sm underline" href="##">
Forgot your password?
</a>
</div>
<Input bind:value={password} id="password" required type="password"/>
</div>
<Card.Header>
<Card.Title class="text-2xl">Login</Card.Title>
<Card.Description>Enter your email below to login to your account</Card.Description>
</Card.Header>
<Card.Content>
<form class="grid gap-4" on:submit={handleLogin}>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input bind:value={email} id="email" placeholder="m@example.com" required type="email"/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a class="ml-auto inline-block text-sm underline" href="##"> Forgot your password? </a>
</div>
<Input bind:value={password} id="password" required type="password"/>
</div>
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{/if}
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{/if}
<Button class="w-full" disabled={isLoading} type="submit">
{#if isLoading}
Logging in...
{:else}
Login
{/if}
</Button>
</form>
<Button class="w-full" disabled={isLoading} type="submit">
{#if isLoading}
Logging in...
{:else}
Login
{/if}
</Button>
</form>
<Button class="w-full mt-2" variant="outline">Login with Google</Button>
<Button class="mt-2 w-full" variant="outline">Login with Google</Button>
<div class="mt-4 text-center text-sm">
Don't have an account?
<a class="underline" href="##"> Sign up </a>
</div>
</Card.Content>
</Card.Root>
<div class="mt-4 text-center text-sm">
Don't have an account?
<a class="underline" href="##"> Sign up </a>
</div>
</Card.Content>
</Card.Root>

View File

@@ -1,66 +1,68 @@
<script lang="ts">
import * as Collapsible from '$lib/components/ui/collapsible/index.js';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
let {
items,
}: {
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;
isActive?: boolean;
items?: {
title: string;
url: string;
}[];
}[];
} = $props();
let {
items
}: {
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;
isActive?: boolean;
items?: {
title: string;
url: string;
}[];
}[];
} = $props();
</script>
<Sidebar.Group>
<Sidebar.GroupLabel>Platform</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each items as mainItem (mainItem.title)}
<Collapsible.Root open={mainItem.isActive} class="group/collapsible">
{#snippet child({props})}
<Sidebar.MenuItem {...props}>
<Collapsible.Trigger>
{#snippet child({props})}
<Sidebar.MenuButton {...props}>
{#snippet tooltipContent()}
{mainItem.title}
{/snippet}
{#if mainItem.icon}
<mainItem.icon/>
{/if}
<span>{mainItem.title}</span>
<ChevronRight
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
/>
</Sidebar.MenuButton>
{/snippet}
</Collapsible.Trigger>
<Collapsible.Content>
{#if mainItem.items}
<Sidebar.MenuSub>
{#each mainItem.items as subItem (subItem.title)}
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton>
{#snippet child({props})}
<a href={subItem.url} {...props}>
<span>{subItem.title}</span>
</a>
{/snippet}
</Sidebar.MenuSubButton>
</Sidebar.MenuSubItem>
{/each}
</Sidebar.MenuSub>
{/if}
</Collapsible.Content>
</Sidebar.MenuItem>
{/snippet}
</Collapsible.Root>
{/each}
</Sidebar.Menu>
<Sidebar.GroupLabel>Platform</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each items as mainItem (mainItem.title)}
<Collapsible.Root open={mainItem.isActive}>
{#snippet child({props})}
<Sidebar.MenuItem {...props}>
<Sidebar.MenuButton>
{#snippet tooltipContent()}
{mainItem.title}
{/snippet}
{#snippet child({props})}
<a href={mainItem.url} {...props}>
<mainItem.icon/>
<span>{mainItem.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
{#if mainItem.items?.length}
<Collapsible.Trigger>
{#snippet child({props})}
<Sidebar.MenuAction {...props} class="data-[state=open]:rotate-90">
<ChevronRight/>
<span class="sr-only">Toggle</span>
</Sidebar.MenuAction>
{/snippet}
</Collapsible.Trigger>
<Collapsible.Content>
<Sidebar.MenuSub>
{#each mainItem.items as subItem (subItem.title)}
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton href={subItem.url}>
<span>{subItem.title}</span>
</Sidebar.MenuSubButton>
</Sidebar.MenuSubItem>
{/each}
</Sidebar.MenuSub>
</Collapsible.Content>
{/if}
</Sidebar.MenuItem>
{/snippet}
</Collapsible.Root>
{/each}
</Sidebar.Menu>
</Sidebar.Group>

View File

@@ -1,70 +1,76 @@
<script lang="ts">
import {useSidebar} from "$lib/components/ui/sidebar/context.svelte.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 Ellipsis from '@lucide/svelte/icons/ellipsis';
import Folder from '@lucide/svelte/icons/folder';
import Share from '@lucide/svelte/icons/share';
import Trash2 from '@lucide/svelte/icons/trash-2';
let {
projects,
}: {
projects: {
name: string;
url: string;
// This should be `Component` after @lucide/svelte updates types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
icon: any;
}[];
} = $props();
let {
projects
}: {
projects: {
name: string;
url: string;
// This should be `Component` after @lucide/svelte updates types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
icon: any;
}[];
} = $props();
const sidebar = useSidebar();
const sidebar = useSidebar();
</script>
<Sidebar.Group class="group-data-[collapsible=icon]:hidden">
<Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each projects as item (item.name)}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({props})}
<a href={item.url} {...props}>
<item.icon/>
<span>{item.name}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({props})}
<Sidebar.MenuAction showOnHover {...props}>
<Ellipsis/>
<span class="sr-only">More</span>
</Sidebar.MenuAction>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-48 rounded-lg"
side={sidebar.isMobile ? "bottom" : "right"}
align={sidebar.isMobile ? "end" : "start"}
>
<DropdownMenu.Item>
<Folder class="text-muted-foreground"/>
<span>View Project</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<Forward class="text-muted-foreground"/>
<span>Share Project</span>
</DropdownMenu.Item>
<DropdownMenu.Separator/>
<DropdownMenu.Item>
<Trash2 class="text-muted-foreground"/>
<span>Delete Project</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
{/each}
<Sidebar.MenuItem>
<Sidebar.MenuButton class="text-sidebar-foreground/70">
<Ellipsis class="text-sidebar-foreground/70"/>
<span>More</span>
</Sidebar.MenuButton>
</Sidebar.MenuItem>
</Sidebar.Menu>
<Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each projects as item (item.name)}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({props})}
<a href={item.url} {...props}>
<item.icon/>
<span>{item.name}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({props})}
<Sidebar.MenuAction showOnHover {...props}>
<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'}
>
<DropdownMenu.Item>
<Folder class="text-muted-foreground"/>
<span>View Project</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<Share class="text-muted-foreground"/>
<span>Share Project</span>
</DropdownMenu.Item>
<DropdownMenu.Separator/>
<DropdownMenu.Item>
<Trash2 class="text-muted-foreground"/>
<span>Delete Project</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
{/each}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
<Ellipsis/>
<span>More</span>
</Sidebar.MenuButton>
</Sidebar.MenuItem>
</Sidebar.Menu>
</Sidebar.Group>

View File

@@ -0,0 +1,37 @@
<script lang="ts">
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import type {ComponentProps} from 'svelte';
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 bind:ref {...restProps}>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each items as item (item.title)}
<Sidebar.MenuItem>
<Sidebar.MenuButton size="sm">
{#snippet child({props})}
<a href={item.url} {...props}>
<item.icon/>
<span>{item.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>

View File

@@ -1,69 +1,89 @@
<script lang="ts">
import {useSidebar} from "$lib/components/ui/sidebar/index.js";
import UserDetails from "$lib/components/user-details.svelte"
import BadgeCheck from '@lucide/svelte/icons/badge-check';
import Bell from '@lucide/svelte/icons/bell';
import ChevronsUpDown from '@lucide/svelte/icons/chevrons-up-down';
import CreditCard from '@lucide/svelte/icons/credit-card';
import LogOut from '@lucide/svelte/icons/log-out';
import Sparkles from '@lucide/svelte/icons/sparkles';
//let { user }: { user: { name: string; email: string; avatar: string } } = $props();
const sidebar = useSidebar();
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 {getContext} from 'svelte';
import UserDetails from './user-details.svelte';
import type {User} from '$lib/types';
const user: () => User = getContext('user');
const sidebar = useSidebar();
</script>
<Sidebar.Menu>
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({props})}
<Sidebar.MenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
{...props}
>
<div class="grid flex-1 text-left text-sm leading-tight">
<UserDetails/>
</div>
<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}
>
<DropdownMenu.Label class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<div class="grid flex-1 text-left text-sm leading-tight">
<UserDetails/>
</div>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator/>
<DropdownMenu.Group>
<DropdownMenu.Item>
<Sparkles/>
Upgrade to Pro
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator/>
<DropdownMenu.Group>
<DropdownMenu.Item>
<BadgeCheck/>
Account
</DropdownMenu.Item>
<DropdownMenu.Item>
<CreditCard/>
Billing
</DropdownMenu.Item>
<DropdownMenu.Item>
<Bell/>
Notifications
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator/>
<DropdownMenu.Item>
<LogOut/>
Log out
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({props})}
<Sidebar.MenuButton
{...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">CN</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<UserDetails/>
</div>
<ChevronsUpDown class="ml-auto size-4"/>
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-[var(--bits-dropdown-menu-anchor-width)] min-w-56 rounded-lg"
side={sidebar.isMobile ? 'bottom' : 'right'}
align="end"
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">CN</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<UserDetails/>
</div>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator/>
<DropdownMenu.Group>
<DropdownMenu.Item>
<Sparkles/>
Upgrade to Pro
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator/>
<DropdownMenu.Group>
<DropdownMenu.Item>
<BadgeCheck/>
Account
</DropdownMenu.Item>
<DropdownMenu.Item>
<CreditCard/>
Billing
</DropdownMenu.Item>
<DropdownMenu.Item>
<Bell/>
Notifications
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator/>
<DropdownMenu.Item>
<LogOut/>
Log out
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
</Sidebar.Menu>

View File

@@ -1,6 +1,6 @@
<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,
@@ -10,7 +10,7 @@
</script>
<AvatarPrimitive.Fallback
{...restProps}
bind:ref
class={cn("bg-muted flex size-full items-center justify-center", className)}
class={cn('flex size-full items-center justify-center bg-muted', className)}
{...restProps}
/>

View File

@@ -1,6 +1,6 @@
<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,
@@ -12,9 +12,9 @@
</script>
<AvatarPrimitive.Image
{...restProps}
{alt}
bind:ref
class={cn("aspect-square size-full", className)}
{src}
{alt}
class={cn('aspect-square size-full', className)}
{...restProps}
/>

View File

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

View File

@@ -1,6 +1,6 @@
import Root from "./avatar.svelte";
import Image from "./avatar-image.svelte";
import Fallback from "./avatar-fallback.svelte";
import Root from './avatar.svelte';
import Image from './avatar-image.svelte';
import Fallback from './avatar-fallback.svelte';
export {
Root,
@@ -9,5 +9,5 @@ export {
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback,
Fallback as AvatarFallback
};

View File

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

View File

@@ -1,7 +1,7 @@
<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),
@@ -11,6 +11,6 @@
}: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li {...restProps} bind:this={ref} class={cn("inline-flex items-center gap-1.5", className)}>
<li bind:this={ref} class={cn('inline-flex items-center gap-1.5', className)} {...restProps}>
{@render children?.()}
</li>

View File

@@ -1,8 +1,8 @@
<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),
@@ -16,9 +16,9 @@
} = $props();
const attrs = $derived({
class: cn("hover:text-foreground transition-colors", className),
class: cn('hover:text-foreground transition-colors', className),
href,
...restProps,
...restProps
});
</script>

View File

@@ -1,7 +1,7 @@
<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),
@@ -12,12 +12,12 @@
</script>
<ol
{...restProps}
bind:this={ref}
class={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 break-words text-sm sm:gap-2.5",
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
className
)}
{...restProps}
>
{@render children?.()}
</ol>

View File

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

View File

@@ -1,7 +1,8 @@
<script lang="ts">
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),
@@ -12,11 +13,11 @@
</script>
<li
{...restProps}
aria-hidden="true"
bind:this={ref}
class={cn("[&>svg]:size-3.5", className)}
role="presentation"
aria-hidden="true"
class={cn('[&>svg]:size-3.5', className)}
bind:this={ref}
{...restProps}
>
{#if children}
{@render children?.()}

View File

@@ -1,6 +1,6 @@
<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(),
@@ -10,6 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script>
<nav {...restProps} aria-label="breadcrumb" bind:this={ref} class={className}>
<nav class={className} bind:this={ref} aria-label="breadcrumb" {...restProps}>
{@render children?.()}
</nav>

View File

@@ -1,10 +1,10 @@
import Root from "./breadcrumb.svelte";
import Ellipsis from "./breadcrumb-ellipsis.svelte";
import Item from "./breadcrumb-item.svelte";
import Separator from "./breadcrumb-separator.svelte";
import Link from "./breadcrumb-link.svelte";
import List from "./breadcrumb-list.svelte";
import Page from "./breadcrumb-page.svelte";
import Root from './breadcrumb.svelte';
import Ellipsis from './breadcrumb-ellipsis.svelte';
import Item from './breadcrumb-item.svelte';
import Separator from './breadcrumb-separator.svelte';
import Link from './breadcrumb-link.svelte';
import List from './breadcrumb-list.svelte';
import Page from './breadcrumb-page.svelte';
export {
Root,
@@ -21,5 +21,5 @@ export {
Separator as BreadcrumbSeparator,
Link as BreadcrumbLink,
List as BreadcrumbList,
Page as BreadcrumbPage,
Page as BreadcrumbPage
};

View File

@@ -1,36 +1,35 @@
<script lang="ts" module>
import type {WithElementRef} from "bits-ui";
import type {HTMLAnchorAttributes, HTMLButtonAttributes} from "svelte/elements";
import {tv, type VariantProps} 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",
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',
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm",
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",
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",
'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'
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9'
}
},
defaultVariants: {
variant: "default",
size: "default",
},
variant: 'default',
size: 'default'
}
});
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
@@ -40,27 +39,22 @@
</script>
<script lang="ts">
import {cn} from "$lib/utils.js";
import {cn} from '$lib/utils.js';
let {
class: className,
variant = "default",
size = "default",
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = "button",
type = 'button',
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
class={cn(buttonVariants({ variant, size }), className)}
{href}
{...restProps}
>
<a bind:this={ref} class={cn(buttonVariants({ variant, size }), className)} {href} {...restProps}>
{@render children?.()}
</a>
{:else}

View File

@@ -1,4 +1,9 @@
import Root, {type ButtonProps, type ButtonSize, type ButtonVariant, buttonVariants,} from "./button.svelte";
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
} from './button.svelte';
export {
Root,
@@ -8,5 +13,5 @@ export {
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant,
type ButtonVariant
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import Root from "./card.svelte";
import Content from "./card-content.svelte";
import Description from "./card-description.svelte";
import Footer from "./card-footer.svelte";
import Header from "./card-header.svelte";
import Title from "./card-title.svelte";
import Root from './card.svelte';
import Content from './card-content.svelte';
import Description from './card-description.svelte';
import Footer from './card-footer.svelte';
import Header from './card-header.svelte';
import Title from './card-title.svelte';
export {
Root,
@@ -18,5 +18,5 @@ export {
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
Title as CardTitle
};

View File

@@ -1,4 +1,4 @@
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;
@@ -11,5 +11,5 @@ export {
//
Root as Collapsible,
Content as CollapsibleContent,
Trigger as CollapsibleTrigger,
Trigger as CollapsibleTrigger
};

View File

@@ -0,0 +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';
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(
'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>
</Dialog.Portal>

View File

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

View File

@@ -0,0 +1,20 @@
<script lang="ts">
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();
</script>
<div
bind:this={ref}
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
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();
</script>
<div
bind:this={ref}
class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import {Dialog as DialogPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
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}
/>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import {Dialog as DialogPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
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}
/>

View File

@@ -0,0 +1,37 @@
import {Dialog as DialogPrimitive} from 'bits-ui';
import Title from './dialog-title.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
const Root: typeof DialogPrimitive.Root = DialogPrimitive.Root;
const Trigger: typeof DialogPrimitive.Trigger = DialogPrimitive.Trigger;
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
};

View File

@@ -1,7 +1,9 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild} from "bits-ui";
import {cn} from "$lib/utils.js";
import type {Snippet} from "svelte";
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';
let {
ref = $bindable(null),
@@ -16,21 +18,21 @@
</script>
<DropdownMenuPrimitive.CheckboxItem
{...restProps}
bind:ref
bind:checked
bind:indeterminate
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground 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-[disabled]:opacity-50",
'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}
>
{#snippet children({checked, indeterminate})}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<Minus class="size-4"/>
{:else}
<Check class={cn("size-4", !checked && "text-transparent")}/>
<Check class={cn('size-4', !checked && 'text-transparent')}/>
{/if}
</span>
{@render childrenProp?.()}

View File

@@ -1,6 +1,6 @@
<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),
@@ -15,13 +15,13 @@
<DropdownMenuPrimitive.Portal {...portalProps}>
<DropdownMenuPrimitive.Content
{...restProps}
bind:ref
{sideOffset}
class={cn(
"bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md",
"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 outline-none",
'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
)}
{sideOffset}
{...restProps}
/>
</DropdownMenuPrimitive.Portal>

View File

@@ -1,6 +1,6 @@
<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),
@@ -13,7 +13,7 @@
</script>
<DropdownMenuPrimitive.GroupHeading
{...restProps}
bind:ref
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
/>

View File

@@ -1,6 +1,6 @@
<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),
@@ -13,11 +13,11 @@
</script>
<DropdownMenuPrimitive.Item
{...restProps}
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground 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-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
'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}
/>

View File

@@ -1,7 +1,7 @@
<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),
@@ -15,9 +15,9 @@
</script>
<div
{...restProps}
bind:this={ref}
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChild} from "bits-ui";
import {cn} from "$lib/utils.js";
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChild} from 'bits-ui';
import Circle from '@lucide/svelte/icons/circle';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -11,12 +12,12 @@
</script>
<DropdownMenuPrimitive.RadioItem
{...restProps}
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground 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-[disabled]:opacity-50",
'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}
>
{#snippet children({checked})}
<span class="absolute left-2 flex size-3.5 items-center justify-center">

View File

@@ -1,6 +1,6 @@
<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),
@@ -10,7 +10,7 @@
</script>
<DropdownMenuPrimitive.Separator
{...restProps}
bind:ref
class={cn("bg-muted -mx-1 my-1 h-px", className)}
class={cn('-mx-1 my-1 h-px bg-muted', className)}
{...restProps}
/>

View File

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

View File

@@ -1,6 +1,6 @@
<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),
@@ -10,10 +10,10 @@
</script>
<DropdownMenuPrimitive.SubContent
{...restProps}
bind:ref
class={cn(
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none",
'z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none',
className
)}
{...restProps}
/>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import {DropdownMenu as DropdownMenuPrimitive, type WithoutChild} from "bits-ui";
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),
@@ -14,13 +15,13 @@
</script>
<DropdownMenuPrimitive.SubTrigger
{...restProps}
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
'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}
>
{@render children?.()}
<ChevronRight class="ml-auto"/>

View File

@@ -1,14 +1,14 @@
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";
import Item from "./dropdown-menu-item.svelte";
import Label from "./dropdown-menu-label.svelte";
import RadioItem from "./dropdown-menu-radio-item.svelte";
import Separator from "./dropdown-menu-separator.svelte";
import Shortcut from "./dropdown-menu-shortcut.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
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';
import Item from './dropdown-menu-item.svelte';
import Label from './dropdown-menu-label.svelte';
import RadioItem from './dropdown-menu-radio-item.svelte';
import Separator from './dropdown-menu-separator.svelte';
import Shortcut from './dropdown-menu-shortcut.svelte';
import SubContent from './dropdown-menu-sub-content.svelte';
import SubTrigger from './dropdown-menu-sub-trigger.svelte';
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
@@ -46,5 +46,5 @@ export {
Sub,
SubContent,
SubTrigger,
Trigger,
Trigger
};

View File

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

View File

@@ -1,13 +1,13 @@
<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 })
Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
>;
let {
@@ -20,11 +20,11 @@
}: Props = $props();
</script>
{#if type === "file"}
{#if type === 'file'}
<input
bind:this={ref}
class={cn(
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
'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"
@@ -36,7 +36,7 @@
<input
bind:this={ref}
class={cn(
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
'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}

View File

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

View File

@@ -1,6 +1,6 @@
<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),
@@ -13,7 +13,7 @@
{...restProps}
bind:ref
class={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
className
)}
/>

View File

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

View File

@@ -0,0 +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';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> & {
value: string;
} = $props();
</script>
<RadioGroupPrimitive.Item
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}
>
{#snippet children({checked})}
<div class="flex items-center justify-center">
{#if checked}
<Circle class="size-3.5 fill-primary"/>
{/if}
</div>
{/snippet}
</RadioGroupPrimitive.Item>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
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();
</script>
<RadioGroupPrimitive.Root bind:value class={cn('grid gap-2', className)} {...restProps} bind:ref/>

View File

@@ -0,0 +1,34 @@
import {Select as SelectPrimitive} from 'bits-ui';
import GroupHeading from './select-group-heading.svelte';
import Item from './select-item.svelte';
import Content from './select-content.svelte';
import Trigger from './select-trigger.svelte';
import Separator from './select-separator.svelte';
import ScrollDownButton from './select-scroll-down-button.svelte';
import ScrollUpButton from './select-scroll-up-button.svelte';
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
};

View File

@@ -0,0 +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';
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(
'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(
'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>
</SelectPrimitive.Portal>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import {Select as SelectPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
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}
/>

View File

@@ -0,0 +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';
let {
ref = $bindable(null),
class: className,
value,
label,
children: childrenProp,
...restProps
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
</script>
<SelectPrimitive.Item
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}
>
{#snippet children({selected, highlighted})}
<span class="absolute right-2 flex size-3.5 items-center justify-center">
{#if selected}
<Check class="size-4"/>
{/if}
</span>
{#if childrenProp}
{@render childrenProp({selected, highlighted})}
{:else}
{label || value}
{/if}
{/snippet}
</SelectPrimitive.Item>

View File

@@ -0,0 +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';
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}
>
<ChevronDown class="size-4"/>
</SelectPrimitive.ScrollDownButton>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import ChevronUp from '@lucide/svelte/icons/chevron-up';
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();
</script>
<SelectPrimitive.ScrollUpButton
bind:ref
class={cn('flex cursor-default items-center justify-center py-1', className)}
{...restProps}
>
<ChevronUp class="size-4"/>
</SelectPrimitive.ScrollUpButton>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type {Separator as SeparatorPrimitive} from 'bits-ui';
import {Separator} from '$lib/components/ui/separator/index.js';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SeparatorPrimitive.RootProps = $props();
</script>
<Separator bind:ref class={cn('-mx-1 my-1 h-px bg-muted', className)} {...restProps}/>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import {Select as SelectPrimitive, 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,
children,
...restProps
}: WithoutChild<SelectPrimitive.TriggerProps> = $props();
</script>
<SelectPrimitive.Trigger
bind:ref
class={cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1',
className
)}
{...restProps}
>
{@render children?.()}
<ChevronDown class="size-4 opacity-50"/>
</SelectPrimitive.Trigger>

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import {Dialog as SheetPrimitive} from "bits-ui";
import {Dialog as SheetPrimitive} from 'bits-ui';
import Overlay from "./sheet-overlay.svelte";
import Content from "./sheet-content.svelte";
import Header from "./sheet-header.svelte";
import Footer from "./sheet-footer.svelte";
import Title from "./sheet-title.svelte";
import Description from "./sheet-description.svelte";
import Overlay from './sheet-overlay.svelte';
import Content from './sheet-content.svelte';
import Header from './sheet-header.svelte';
import Footer from './sheet-footer.svelte';
import Title from './sheet-title.svelte';
import Description from './sheet-description.svelte';
const Root = SheetPrimitive.Root;
const Close = SheetPrimitive.Close;
@@ -33,5 +33,5 @@ export {
Header as SheetHeader,
Footer as SheetFooter,
Title as SheetTitle,
Description as SheetDescription,
Description as SheetDescription
};

View File

@@ -1,36 +1,38 @@
<script lang="ts" module>
import {tv, type VariantProps} from "tailwind-variants";
import {type VariantProps, tv} from 'tailwind-variants';
export const sheetVariants = tv({
base: "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 gap-4 p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
base: 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 gap-4 p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
variants: {
side: {
top: "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 border-b",
bottom: "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 border-t",
left: "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
right: "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
},
top: 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 border-b',
bottom:
'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 border-t',
left: 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
right:
'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm'
}
},
defaultVariants: {
side: "right",
},
side: 'right'
}
});
export type Side = VariantProps<typeof sheetVariants>["side"];
export type Side = VariantProps<typeof sheetVariants>['side'];
</script>
<script lang="ts">
import {Dialog as SheetPrimitive, type WithoutChildrenOrChild} from "bits-ui";
import X from "@lucide/svelte/icons/x";
import type {Snippet} from "svelte";
import SheetOverlay from "./sheet-overlay.svelte";
import {cn} from "$lib/utils.js";
import {Dialog as SheetPrimitive, type WithoutChildrenOrChild} from 'bits-ui';
import X from '@lucide/svelte/icons/x';
import type {Snippet} from 'svelte';
import SheetOverlay from './sheet-overlay.svelte';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
portalProps,
side = "right",
side = 'right',
children,
...restProps
}: WithoutChildrenOrChild<SheetPrimitive.ContentProps> & {
@@ -42,10 +44,10 @@
<SheetPrimitive.Portal {...portalProps}>
<SheetOverlay/>
<SheetPrimitive.Content {...restProps} bind:ref class={cn(sheetVariants({ side }), className)}>
<SheetPrimitive.Content bind:ref class={cn(sheetVariants({ side }), className)} {...restProps}>
{@render children?.()}
<SheetPrimitive.Close
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
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-secondary"
>
<X class="size-4"/>
<span class="sr-only">Close</span>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import {Dialog as SheetPrimitive} from "bits-ui";
import {cn} from "$lib/utils.js";
import {Dialog as SheetPrimitive} from 'bits-ui';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -10,7 +10,7 @@
</script>
<SheetPrimitive.Title
{...restProps}
bind:ref
class={cn("text-foreground text-lg font-semibold", className)}
class={cn('text-lg font-semibold text-foreground', className)}
{...restProps}
/>

View File

@@ -1,6 +1,6 @@
export const SIDEBAR_COOKIE_NAME = "sidebar:state";
export const SIDEBAR_COOKIE_NAME = 'sidebar:state';
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
export const SIDEBAR_WIDTH = "16rem";
export const SIDEBAR_WIDTH_MOBILE = "18rem";
export const SIDEBAR_WIDTH_ICON = "3rem";
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
export const SIDEBAR_WIDTH = '16rem';
export const SIDEBAR_WIDTH_MOBILE = '18rem';
export const SIDEBAR_WIDTH_ICON = '3rem';
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b';

View File

@@ -1,6 +1,6 @@
import {IsMobile} from "$lib/hooks/is-mobile.svelte.js";
import {getContext, setContext} from "svelte";
import {SIDEBAR_KEYBOARD_SHORTCUT} from "./constants.js";
import {IsMobile} from '$lib/hooks/is-mobile.svelte.js';
import {getContext, setContext} from 'svelte';
import {SIDEBAR_KEYBOARD_SHORTCUT} from './constants.js';
type Getter<T> = () => T;
@@ -24,9 +24,9 @@ class SidebarState {
readonly props: SidebarStateProps;
open = $derived.by(() => this.props.open());
openMobile = $state(false);
setOpen: SidebarStateProps["setOpen"];
state = $derived.by(() => (this.open ? "expanded" : "collapsed"));
setOpen: SidebarStateProps['setOpen'];
#isMobile: IsMobile;
state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'));
constructor(props: SidebarStateProps) {
this.setOpen = props.setOpen;
@@ -53,13 +53,11 @@ class SidebarState {
};
toggle = () => {
return this.#isMobile.current
? (this.openMobile = !this.openMobile)
: this.setOpen(!this.open);
return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
};
}
const SYMBOL_KEY = "scn-sidebar";
const SYMBOL_KEY = 'scn-sidebar';
/**
* Instantiates a new `SidebarState` instance and sets it in the context.

View File

@@ -1,27 +1,27 @@
import {useSidebar} from "./context.svelte.js";
import Content from "./sidebar-content.svelte";
import Footer from "./sidebar-footer.svelte";
import GroupAction from "./sidebar-group-action.svelte";
import GroupContent from "./sidebar-group-content.svelte";
import GroupLabel from "./sidebar-group-label.svelte";
import Group from "./sidebar-group.svelte";
import Header from "./sidebar-header.svelte";
import Input from "./sidebar-input.svelte";
import Inset from "./sidebar-inset.svelte";
import MenuAction from "./sidebar-menu-action.svelte";
import MenuBadge from "./sidebar-menu-badge.svelte";
import MenuButton from "./sidebar-menu-button.svelte";
import MenuItem from "./sidebar-menu-item.svelte";
import MenuSkeleton from "./sidebar-menu-skeleton.svelte";
import MenuSubButton from "./sidebar-menu-sub-button.svelte";
import MenuSubItem from "./sidebar-menu-sub-item.svelte";
import MenuSub from "./sidebar-menu-sub.svelte";
import Menu from "./sidebar-menu.svelte";
import Provider from "./sidebar-provider.svelte";
import Rail from "./sidebar-rail.svelte";
import Separator from "./sidebar-separator.svelte";
import Trigger from "./sidebar-trigger.svelte";
import Root from "./sidebar.svelte";
import {useSidebar} from './context.svelte.js';
import Content from './sidebar-content.svelte';
import Footer from './sidebar-footer.svelte';
import GroupAction from './sidebar-group-action.svelte';
import GroupContent from './sidebar-group-content.svelte';
import GroupLabel from './sidebar-group-label.svelte';
import Group from './sidebar-group.svelte';
import Header from './sidebar-header.svelte';
import Input from './sidebar-input.svelte';
import Inset from './sidebar-inset.svelte';
import MenuAction from './sidebar-menu-action.svelte';
import MenuBadge from './sidebar-menu-badge.svelte';
import MenuButton from './sidebar-menu-button.svelte';
import MenuItem from './sidebar-menu-item.svelte';
import MenuSkeleton from './sidebar-menu-skeleton.svelte';
import MenuSubButton from './sidebar-menu-sub-button.svelte';
import MenuSubItem from './sidebar-menu-sub-item.svelte';
import MenuSub from './sidebar-menu-sub.svelte';
import Menu from './sidebar-menu.svelte';
import Provider from './sidebar-provider.svelte';
import Rail from './sidebar-rail.svelte';
import Separator from './sidebar-separator.svelte';
import Trigger from './sidebar-trigger.svelte';
import Root from './sidebar.svelte';
export {
Content,
@@ -71,5 +71,5 @@ export {
Separator as SidebarSeparator,
Trigger as SidebarTrigger,
Trigger,
useSidebar,
useSidebar
};

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type {HTMLAttributes} from "svelte/elements";
import type {WithElementRef} from "bits-ui";
import {cn} from "$lib/utils.js";
import type {HTMLAttributes} from 'svelte/elements';
import type {WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,13 +12,13 @@
</script>
<div
{...restProps}
bind:this={ref}
data-sidebar="content"
class={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
className
)}
data-sidebar="content"
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type {HTMLAttributes} from "svelte/elements";
import type {WithElementRef} from "bits-ui";
import {cn} from "$lib/utils.js";
import type {HTMLAttributes} from 'svelte/elements';
import type {WithElementRef} from 'bits-ui';
import {cn} from '$lib/utils.js';
let {
ref = $bindable(null),
@@ -12,10 +12,10 @@
</script>
<div
{...restProps}
bind:this={ref}
class={cn("flex flex-col gap-2 p-2", className)}
data-sidebar="footer"
class={cn('flex flex-col gap-2 p-2', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import {cn} from "$lib/utils.js";
import type {WithElementRef} from "bits-ui";
import type {Snippet} from "svelte";
import type {HTMLButtonAttributes} from "svelte/elements";
import {cn} from '$lib/utils.js';
import type {WithElementRef} from 'bits-ui';
import type {Snippet} from 'svelte';
import type {HTMLButtonAttributes} from 'svelte/elements';
let {
ref = $bindable(null),
@@ -16,14 +16,14 @@
const propObj = $derived({
class: cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-none transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-none transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden",
'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden',
className
),
"data-sidebar": "group-action",
...restProps,
'data-sidebar': 'group-action',
...restProps
});
</script>

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