stashing code

This commit is contained in:
user
2025-10-20 17:07:41 +03:00
commit f5b99afc8f
890 changed files with 54823 additions and 0 deletions

45
.dockerignore Normal file
View File

@@ -0,0 +1,45 @@
.zed
# Dependencies
node_modules
.pnp
.pnp.js
__pycache__
.venv
# ignore generated log files
**/logs/**.log
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Testing
coverage
# Turbo
.turbo
# Vercel
.vercel
# Build Outputs
.next/
out/
build
dist
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem
creds.md

61
.gitignore vendored Normal file
View File

@@ -0,0 +1,61 @@
.zed
# Dependencies
node_modules
.pnp
.pnp.js
__pycache__
.venv
# ignore generated log files
**/logs/**.log
ot_res.json
out.json
payload.json
skin.json
skyscanner.json
skyscanner.har
skyscanner-airport-search.har
screenshots/*.jpeg
screenshots/*.png
screenshots/*.jpg
scripts/ssh.login.sh
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Testing
coverage
# Turbo
.turbo
# Vercel
.vercel
# Build Outputs
.next/
out/
build
dist
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem
creds.md
onlydevs

0
.npmrc Normal file
View File

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
# What is this?
FLIGHT SIMULATOR 💥✈️🏢🏢💥
Ok but fo' real dis wus a project that I made to learn more around monorepos and websocket or well live data sync in general, in particular the checkout data was synced and controllable by the admin for this.
---

23
apps/admin/.gitignore vendored Normal file
View File

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

1
apps/admin/.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

View File

@@ -0,0 +1,4 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

5
apps/admin/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Overview
The payment panel app for customers to send payments to the integrated merchants.
---

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://next.shadcn-svelte.com/schema.json",
"style": "default",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app.css",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks"
},
"typescript": true,
"registry": "https://next.shadcn-svelte.com/registry"
}

94
apps/admin/package.json Normal file
View File

@@ -0,0 +1,94 @@
{
"name": "@domain-wall/admin",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev --port 5173",
"build": "vite build",
"prod": "HOST=0.0.0.0 PORT=3000 bun ./build/index.js",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest run",
"test": "npm run test:unit -- --run",
"auth:schemagen": "bun x @better-auth/cli generate --config ./src/lib/domains/auth/config/base.ts --output ../../packages/db/schema/auth.out.ts",
"format": "prettier --write .",
"lint": "prettier --check ."
},
"devDependencies": {
"@iconify/json": "^2.2.275",
"@internationalized/date": "^3.6.0",
"@lucide/svelte": "^0.503.0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/chart.js": "^2.9.41",
"@types/date-fns": "^2.6.3",
"@types/node": "^22.9.3",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.4.2",
"clsx": "^2.1.1",
"embla-carousel-svelte": "^8.6.0",
"mode-watcher": "^0.5.0",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.9",
"svelte": "^5.0.0",
"svelte-adapter-bun": "^0.5.2",
"svelte-check": "^4.0.0",
"svelte-sonner": "^0.3.28",
"tailwind-merge": "^2.5.4",
"tailwind-variants": "^0.3.0",
"tailwindcss": "^3.4.9",
"typescript": "^5.0.0",
"vite": "^5.0.3",
"vitest": "^2.0.4"
},
"dependencies": {
"@better-fetch/fetch": "^1.1.12",
"@pkg/db": "workspace:*",
"@pkg/logger": "workspace:*",
"@pkg/logic": "workspace:*",
"@pkg/result": "workspace:*",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/svelte-query": "^5.62.3",
"@tanstack/table-core": "^8.20.5",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"argon2": "^0.41.1",
"better-auth": "^1.2.7",
"chart.js": "^4.4.9",
"date-fns": "^4.1.0",
"ioredis": "^5.4.1",
"nanoid": "^5.0.8",
"runed": "^0.23.0",
"tailwindcss-animate": "^1.0.7",
"trpc-svelte-query-adapter": "^2.3.15",
"trpc-sveltekit": "^3.6.2",
"unplugin-icons": "^0.20.1",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
"zod": "^3.23.8"
},
"prettier": {
"useTabs": true,
"tabWidth": 4,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 82,
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

80
apps/admin/src/app.css Normal file
View File

@@ -0,0 +1,80 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: "Sen";
src: url("/fonts/sen-variable.ttf") format("truetype");
}
@layer base {
:root {
--background: 256 13% 95%;
--foreground: 256 5% 10%;
--card: 256 13% 90%;
--card-foreground: 256 5% 15%;
--popover: 256 13% 95%;
--popover-foreground: 256 95% 10%;
--primary: 256 57.3% 54.1%;
--primary-foreground: 0 0% 100%;
--secondary: 256 13% 70%;
--secondary-foreground: 0 0% 0%;
--muted: 218 13% 85%;
--muted-foreground: 256 5% 40%;
--accent: 218 13% 80%;
--accent-foreground: 256 5% 15%;
--destructive: 0 50% 42%;
--destructive-foreground: 256 5% 90%;
--border: 256 30% 80%;
--input: 256 20% 42%;
--ring: 256 57.3% 54.1%;
--radius: 0.75rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
scroll-behavior: smooth;
font-family:
"Sen",
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
}
}
/* For Chrome, Edge, Safari */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For Firefox */
input[type="number"] {
-moz-appearance: textfield;
}

21
apps/admin/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
import type { Session } from "@pkg/logic/domains/auth/session/data/entities";
import type { UserModel } from "@pkg/logic/domains/user/data/entities";
import "unplugin-icons/types/svelte";
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
interface Locals {
session?: Session;
user?: UserModel;
}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

13
apps/admin/src/app.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,59 @@
import type { Handle, HandleServerError } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";
import { auth } from "$lib/domains/auth/config/base";
import { svelteKitHandler } from "better-auth/svelte-kit";
import type { UserModel } from "@pkg/logic/domains/user/data/entities";
import { createTRPCHandle } from "trpc-sveltekit";
import { router } from "$lib/trpc/router";
import { createContext } from "$lib/trpc/context";
export const handleError: HandleServerError = async ({ error, event }) => {
console.log("[-] Running error middleware for : ", event.url.pathname);
console.log(error);
return { message: (error as Error).message ?? "Internal Server Error" };
};
export const zero: Handle = async ({ event, resolve }) => {
return svelteKitHandler({ event, resolve, auth });
};
export const first: Handle = async ({ event, resolve }) => {
if (
event.url.pathname.includes("/api/auth") ||
event.url.pathname.includes("/api/debug") ||
event.url.pathname.includes("/api/calling") ||
event.url.pathname.includes("/auth")
) {
return await resolve(event);
}
console.log("[+] Running middleware for : ", event.url.pathname);
const baseUrl = event.url.origin;
const signInUrl = baseUrl + "/auth/login";
const isSignInPage = event.url.pathname === "/auth/login";
const redirectResponse = new Response(null, {
status: 302,
headers: { Location: signInUrl },
});
const u = await auth.api.getSession({
headers: event.request.headers,
});
if (!u || !u.user || !u.session) {
return redirectResponse;
}
const user = u.user as any as UserModel;
if (user && isSignInPage) {
return new Response(null, {
status: 302,
headers: { Location: baseUrl },
});
}
event.locals.session = u.session;
event.locals.user = user;
return resolve(event);
};
export const second: Handle = createTRPCHandle({ router, createContext });
export const handle = sequence(zero, first, second);

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import LoaderCircleIcon from "~icons/lucide/loader-circle";
import Icon from "./icon.svelte";
let {
loading,
loadingText,
text,
}: { loading: boolean; loadingText: string; text: string } = $props();
</script>
{#if loading}
<Icon icon={LoaderCircleIcon} cls="h-5 w-auto animate-spin" />
{loadingText}
{:else}
{text}
{/if}

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn } from "$lib/utils";
let {
wrapperClass = "",
containerClass = "flex w-full flex-col gap-4",
children,
}: {
wrapperClass?: string;
containerClass?: string;
children?: any;
} = $props();
</script>
<section
class={cn(
"rounded-xl border-2 border-border/50 bg-white p-4 drop-shadow-md md:p-8",
containerClass,
wrapperClass,
)}
>
{@render children?.()}
</section>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import Button from "../ui/button/button.svelte";
import ArrowLeftIcon from "~icons/solar/arrow-left-broken";
import Icon from "./icon.svelte";
function goBack() {
window.history.back();
}
</script>
<Button onclick={goBack} variant="ghost" size="sm">
<Icon icon={ArrowLeftIcon} cls="h-5 w-auto" />
Go Back
</Button>

View File

@@ -0,0 +1,11 @@
<script lang="ts">
let {
icon: Icon,
cls,
}: {
icon: any;
cls?: string;
} = $props();
</script>
<Icon class={cls} />

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import Label from "$lib/components/ui/label/label.svelte";
import { cn } from "$lib/utils";
let {
label,
labelCls = "pl-2",
wrapperCls = "",
children,
}: {
label: string;
labelCls?: string;
wrapperCls?: string;
children?: any;
} = $props();
</script>
<div class={cn("flex w-full flex-col gap-2", wrapperCls)}>
<Label class={cn(labelCls)}>{label}</Label>
{@render children?.()}
</div>

View File

@@ -0,0 +1,104 @@
<script lang="ts">
import { cn } from "$lib/utils";
interface Props {
cls?: string;
}
let { cls = "" }: Props = $props();
</script>
<div class={cn("relative grid w-full place-items-center", cls)}>
<div class="loader">
<div class="square" id="sq1"></div>
<div class="square" id="sq2"></div>
<div class="square" id="sq3"></div>
<div class="square" id="sq4"></div>
<div class="square" id="sq5"></div>
<div class="square" id="sq6"></div>
<div class="square" id="sq7"></div>
<div class="square" id="sq8"></div>
<div class="square" id="sq9"></div>
</div>
</div>
<style>
/* From Uiverse.io by JkHuger */
@keyframes loader_5191 {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.square {
width: 10px;
height: 10px;
position: absolute;
margin-top: -5px;
margin-left: -5px;
}
@media (prefers-color-scheme: dark) {
.square {
background-color: #fff;
}
}
@media (prefers-color-scheme: light) {
.square {
background-color: #000;
}
}
#sq1 {
margin-top: -25px;
margin-left: -25px;
animation: loader_5191 675ms ease-in-out 0s infinite alternate;
}
#sq2 {
margin-top: -25px;
animation: loader_5191 675ms ease-in-out 75ms infinite alternate;
}
#sq3 {
margin-top: -25px;
margin-left: 15px;
animation: loader_5191 675ms ease-in-out 150ms infinite;
}
#sq4 {
margin-left: -25px;
animation: loader_5191 675ms ease-in-out 225ms infinite;
}
#sq5 {
animation: loader_5191 675ms ease-in-out 300ms infinite;
}
#sq6 {
margin-left: 15px;
animation: loader_5191 675ms ease-in-out 375ms infinite;
}
#sq7 {
margin-top: 15px;
margin-left: -25px;
animation: loader_5191 675ms ease-in-out 450ms infinite;
}
#sq8 {
margin-top: 15px;
animation: loader_5191 675ms ease-in-out 525ms infinite;
}
#sq9 {
margin-top: 15px;
margin-left: 15px;
animation: loader_5191 675ms ease-in-out 600ms infinite;
}
</style>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { mode } from "mode-watcher";
interface Props {
cls?: string;
}
let { cls = "w-auto h-8 md:h-10 lg:h-12 xl:h-14 object-contain" }: Props =
$props();
</script>
{#if $mode === "light"}
<img src="/logo-normal.png" alt="Logo" class={cls} />
{:else}
<img src="/logo-white.png" alt="Logo" class={cls} />
{/if}

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import SunIcon from "~icons/solar/sun-broken";
import MoonIcon from "~icons/solar/moon-sleep-broken";
import { resetMode, setMode, mode } from "mode-watcher";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils";
import Icon from "./icon.svelte";
interface Props {
expanded?: boolean;
}
let { expanded = false }: Props = $props();
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger
class={cn(
buttonVariants({
variant: "ghost",
size: expanded ? "default" : "icon",
}),
expanded ? "w-full justify-start" : "",
)}
>
{#if $mode === "light"}
<Icon
icon={SunIcon}
cls="h-6 w-auto rotate-0 scale-100 dark:-rotate-90 dark:scale-0"
/>
{:else}
<Icon
icon={MoonIcon}
cls="h-6 w-auto rotate-90 scale-0 dark:rotate-0 dark:scale-100"
/>
{/if}
{#if expanded}
<p>Theme</p>
{/if}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item onclick={() => setMode("light")}
>Light</DropdownMenu.Item
>
<DropdownMenu.Item onclick={() => setMode("dark")}>Dark</DropdownMenu.Item
>
<DropdownMenu.Item onclick={() => resetMode()}>System</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import { cn } from "$lib/utils";
type TitleSize = "h1" | "h2" | "h3" | "h4" | "h5" | "p";
type TitleFontWeight = "bold" | "semibold" | "normal" | "medium";
const colors = {
theme: "text-brand-900 dark:text-brand-200",
white: "text-white",
black: "text-black",
primaryLight: "text-brand-900",
primaryDark: "text-brand-200",
gradientPrimary:
"bg-gradient-to-br from-brand-600 to-brand-950 dark:from-brand-100 dark:to-brand-400 inline-block text-transparent bg-clip-text leading-normal sm:pb-1 xl:pb-2",
};
const weights = {
bold: "font-bold",
semibold: "font-semibold",
medium: "font-medium",
normal: "font-normal",
} as const;
const sizes = {
h1: "text-6xl md:text-7xl",
h2: "text-5xl lg:text-6xl",
h3: "text-4xl lg:text-5xl",
h4: "text-2xl lg:text-3xl",
h5: "text-xl lg:text-2xl",
p: "text-lg lg:text-xl",
};
let {
size = "h2",
weight = "medium",
capitalize = true,
color = "gradientPrimary",
center = false,
id = undefined,
children,
}: {
size?: TitleSize;
weight?: TitleFontWeight;
capitalize?: boolean;
color?: keyof typeof colors;
center?: boolean;
id?: string;
children?: any;
} = $props();
</script>
<svelte:element
this={size}
class={cn(
sizes[size] || sizes.p,
weights[weight ?? "bold"],
capitalize && "capitalize",
colors[color],
center && "text-center",
)}
{id}
>
{@render children?.()}
</svelte:element>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import Ellipsis from "@lucide/svelte/icons/ellipsis";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { Button } from "$lib/components/ui/button";
let {
actions,
}: {
actions: {
title: string;
action: () => void;
}[];
} = $props();
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button
{...props}
variant="ghostGhosted"
size="icon"
class="relative h-8 w-8 p-0"
>
<span class="sr-only">Open menu</span>
<Ellipsis class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each actions as action}
<DropdownMenu.Item onclick={action.action}>
<p>
{action.title}
</p>
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { Checkbox } from "$lib/components/ui/checkbox";
import type { Writable } from "svelte/store";
interface Props {
checked: Writable<boolean>;
}
let { checked }: Props = $props();
</script>
<Checkbox bind:checked={$checked} />

View File

@@ -0,0 +1,113 @@
<script lang="ts">
import type { Table as TableType } from "@tanstack/table-core";
import * as Table from "$lib/components/ui/table/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import Title from "$lib/components/atoms/title.svelte";
import { FlexRender } from "$lib/components/ui/data-table";
let {
table,
query = "",
hasData,
currPage,
totalPages,
filterFieldPlaceholder,
onPreviousPageClick,
onNextPageClick,
onQueryChange,
filterFieldDisabled = false,
}: {
table: TableType<any>;
query: string;
hasData: boolean;
currPage: number;
totalPages: number;
filterFieldDisabled?: boolean;
filterFieldPlaceholder?: string;
onQueryChange?: (q: string) => void;
onPreviousPageClick?: () => void;
onNextPageClick?: () => void;
} = $props();
function gotoPreviousPage() {
onPreviousPageClick && onPreviousPageClick();
}
function gotoNextPage() {
onNextPageClick && onNextPageClick();
}
</script>
<div class="h-full w-full">
<div class="mb-4 flex flex-col items-center gap-4 sm:flex-row">
<Input
class="w-full md:max-w-xs"
placeholder={filterFieldPlaceholder ?? "Search..."}
type="text"
disabled={filterFieldDisabled}
bind:value={query}
oninput={(e) => {
onQueryChange && onQueryChange(e.currentTarget.value);
}}
/>
</div>
{#if hasData}
<Table.Root class="w-full min-w-[40rem]">
<Table.Header>
{#each table.getHeaderGroups() as headerGroup}
<Table.Row>
{#each headerGroup.headers as header}
<Table.Head>
{#if !header.isPlaceholder}
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row}
<Table.Row>
{#each row.getVisibleCells() as cell}
<Table.Cell>
<FlexRender
content={cell.column.columnDef.cell}
context={cell.getContext()}
/>
</Table.Cell>
{/each}
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
<div class="flex items-center justify-end space-x-2 py-4">
<div class="flex-1 text-sm text-muted-foreground">
Showing page {currPage} of {totalPages}
</div>
<Button
variant="outline"
size="sm"
onclick={gotoPreviousPage}
disabled={currPage <= 1}>Previous</Button
>
<Button
variant="outline"
size="sm"
disabled={currPage >= totalPages}
onclick={gotoNextPage}>Next</Button
>
</div>
{:else}
<div class="grid w-full place-items-center p-4 py-32 md:p-8 md:py-64">
<Title size="h5">No data found</Title>
</div>
{/if}
</div>

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import CalendarIcon from "~icons/solar/calendar-broken";
import {
DateFormatter,
type DateValue,
getLocalTimeZone,
} from "@internationalized/date";
import { cn } from "$lib/utils";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import { TRANSITION_COLORS } from "$lib/core/constants";
import Icon from "../atoms/icon.svelte";
const inputSizes = {
sm: "px-3 py-2 text-sm file:text-sm",
default: "px-4 py-3",
lg: "px-5 py-3 text-lg file:text-lg",
xl: "px-6 py-4 text-xl file:text-xl",
};
const df = new DateFormatter("en-US", {
dateStyle: "long",
});
let contentRef = $state<HTMLElement | null>(null);
let {
value = $bindable(),
inputSize = "default",
}: {
value: DateValue;
inputSize?: keyof typeof inputSizes;
} = $props();
</script>
<Popover.Root>
<Popover.Trigger
class={cn(
"bg-brand-50/10 focus:border-brand-500 focus:bg-brand-50/60 dark:bg-brand-950/85 flex w-full items-center gap-2 rounded-md border-2 border-white shadow-[inset_0_3px_8px_0_rgba(0,0,0,0.15)] file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:border-border/40 dark:shadow-[inset_0_3px_12px_0_rgba(0,0,0,0.50)] dark:focus:border-border",
inputSizes[inputSize],
TRANSITION_COLORS,
value ? "" : "text-muted-foreground",
)}
>
<Icon icon={CalendarIcon} cls="mr-2 h-6 w-auto" />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Popover.Trigger>
<Popover.Content bind:ref={contentRef} class="w-auto p-0">
<Calendar type="single" bind:value />
</Popover.Content>
</Popover.Root>

View File

@@ -0,0 +1,22 @@
<script>
import { cn } from "$lib/utils";
import SidebarButton from "./sidebar/sidebar-button.svelte";
/**
* @typedef {Object} Props
* @property {string} [title]
* @property {boolean} [noBMargin]
*/
/** @type {Props} */
let { title = "Dashboard", noBMargin = false } = $props();
</script>
<div class={cn("flex items-center gap-1 p-2 md:p-0", noBMargin ? "" : "mb-8 ")}>
<div class="hidden md:block">
<SidebarButton />
</div>
<h1 class="text-lg tracking-wider dark:text-primary-foreground/80 lg:text-xl">
{title}
</h1>
</div>

View File

@@ -0,0 +1,94 @@
<script lang="ts">
import NavUser from "$lib/components/molecules/sidebar/nav-user.svelte";
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
import type { ComponentProps } from "svelte";
import SessionIcon from "~icons/carbon/prompt-session";
import BillListIcon from "~icons/solar/bill-list-linear";
import PackageIcon from "~icons/solar/box-broken";
import DashboardIcon from "~icons/solar/laptop-minimalistic-broken";
import UsersIcon from "~icons/solar/users-group-two-rounded-broken";
import HistoryIcon from "~icons/iconamoon/history-light";
import { adminSiteNavMap } from "$lib/core/constants";
import Icon from "$lib/components/atoms/icon.svelte";
import { sessionUserInfo } from "$lib/stores/session.info";
import { SettingsIcon } from "@lucide/svelte";
const mainLinks = [
{
icon: DashboardIcon,
title: "Dashboard",
url: adminSiteNavMap.dashboard,
},
{
icon: SessionIcon,
title: "Live Sessions",
url: adminSiteNavMap.sessions.live,
},
{
icon: HistoryIcon,
title: "Session History",
url: adminSiteNavMap.sessions.history,
},
{
icon: BillListIcon,
title: "Data",
url: adminSiteNavMap.data,
},
{
icon: PackageIcon,
title: "Orders",
url: adminSiteNavMap.orders,
},
{
icon: UsersIcon,
title: "Profile",
url: adminSiteNavMap.profile,
},
{
icon: SettingsIcon,
title: "Settings",
url: adminSiteNavMap.settings,
},
];
let {
ref = $bindable(null),
collapsible = "icon",
...restProps
}: ComponentProps<typeof Sidebar.Root> = $props();
</script>
<Sidebar.Root bind:ref {collapsible} {...restProps}>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Application</Sidebar.GroupLabel>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each mainLinks as item}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href={item.url} {...props}>
<Icon icon={item.icon} cls="w-auto h-6" />
<span>{item.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer>
<NavUser
user={{
username: $sessionUserInfo.username,
email: $sessionUserInfo.email,
}}
/>
</Sidebar.Footer>
<Sidebar.Rail />
</Sidebar.Root>

View File

@@ -0,0 +1,93 @@
<script lang="ts">
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 ChevronsUpDown from "@lucide/svelte/icons/chevrons-up-down";
import UserIcon from "~icons/solar/user-broken";
import LogoutIcon from "~icons/solar/logout-broken";
import Icon from "$lib/components/atoms/icon.svelte";
import { authClient } from "$lib/domains/auth/config/client";
import { goto } from "$app/navigation";
import { adminSiteNavMap } from "$lib/core/constants";
let { user }: { user: { username: string; email: string } } = $props();
const sidebar = useSidebar();
function onProfileClick() {
goto(adminSiteNavMap.profile);
}
async function onLogoutClick() {
console.log("Logging out");
const out = await authClient.signOut();
console.log(out);
window.location.reload();
}
const avatarText = user.username.slice(0, 2).toUpperCase();
</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}
>
<Avatar.Root class="h-8 w-8 rounded-lg">
<Avatar.Fallback class="rounded-lg">
{avatarText}
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">
{user.username}
</span>
<span class="truncate text-xs">{user.email}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-[--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.Fallback class="rounded-lg">
{avatarText}
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">
{user.username}
</span>
<span class="truncate text-xs">{user.email}</span>
</div>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item onclick={onProfileClick}>
<Icon icon={UserIcon} cls="w-auto h-6" />
Profile
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={onLogoutClick}>
<Icon icon={LogoutIcon} cls="w-auto h-6" />
Log out
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
</Sidebar.Menu>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.ActionProps = $props();
</script>
<AlertDialogPrimitive.Action bind:ref class={cn(buttonVariants(), className)} {...restProps} />

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.CancelProps = $props();
</script>
<AlertDialogPrimitive.Cancel
bind:ref
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...restProps}
/>

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive, type WithoutChild } from "bits-ui";
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
portalProps,
...restProps
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
portalProps?: AlertDialogPrimitive.PortalProps;
} = $props();
</script>
<AlertDialogPrimitive.Portal {...portalProps}>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
bind:ref
class={cn(
"bg-background 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%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
className
)}
{...restProps}
/>
</AlertDialogPrimitive.Portal>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.DescriptionProps = $props();
</script>
<AlertDialogPrimitive.Description
bind:ref
class={cn("text-muted-foreground text-sm", 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 { 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 space-y-2 text-center sm:text-left", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

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

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
level = 3,
...restProps
}: AlertDialogPrimitive.TitleProps = $props();
</script>
<AlertDialogPrimitive.Title
bind:ref
class={cn("text-lg font-semibold", className)}
{level}
{...restProps}
/>

View File

@@ -0,0 +1,39 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import Title from "./alert-dialog-title.svelte";
import Action from "./alert-dialog-action.svelte";
import Cancel from "./alert-dialog-cancel.svelte";
import Footer from "./alert-dialog-footer.svelte";
import Header from "./alert-dialog-header.svelte";
import Overlay from "./alert-dialog-overlay.svelte";
import Content from "./alert-dialog-content.svelte";
import Description from "./alert-dialog-description.svelte";
const Root = AlertDialogPrimitive.Root;
const Trigger = AlertDialogPrimitive.Trigger;
const Portal = AlertDialogPrimitive.Portal;
export {
Root,
Title,
Action,
Cancel,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
//
Root as AlertDialog,
Title as AlertDialogTitle,
Action as AlertDialogAction,
Cancel as AlertDialogCancel,
Portal as AlertDialogPortal,
Footer as AlertDialogFooter,
Header as AlertDialogHeader,
Trigger as AlertDialogTrigger,
Overlay as AlertDialogOverlay,
Content as AlertDialogContent,
Description as AlertDialogDescription,
};

View File

@@ -0,0 +1,16 @@
<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("text-sm [&_p]:leading-relaxed", className)} {...restProps}>
{@render children?.()}
</div>

View File

@@ -0,0 +1,25 @@
<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,
level = 5,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
level?: 1 | 2 | 3 | 4 | 5 | 6;
} = $props();
</script>
<div
role="heading"
aria-level={level}
bind:this={ref}
class={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,48 @@
<script lang="ts" module>
import { type VariantProps, tv } from "tailwind-variants";
export const alertVariants = tv({
base: "[&>svg]:text-foreground relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7",
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
success:
"border-emerald-500/50 text-emerald-500 dark:border-emerald-400 [&>svg]:text-emerald-500",
warning:
"border-amber-500/50 text-amber-500 dark:border-amber-400 [&>svg]:text-amber-500",
},
},
defaultVariants: {
variant: "default",
},
});
export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
</script>
<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,
variant = "default",
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
variant?: AlertVariant;
} = $props();
</script>
<div
bind:this={ref}
class={cn(alertVariants({ variant }), className)}
{...restProps}
role="alert"
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,14 @@
import Root from "./alert.svelte";
import Description from "./alert-description.svelte";
import Title from "./alert-title.svelte";
export { alertVariants, type AlertVariant } from "./alert.svelte";
export {
Root,
Description,
Title,
//
Root as Alert,
Description as AlertDescription,
Title as AlertTitle,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import Ellipsis from "@lucide/svelte/icons/ellipsis";
import type { WithElementRef, WithoutChildren } from "bits-ui";
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
</script>
<span
bind:this={ref}
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>
</span>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
<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<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
role="link"
aria-disabled="true"
aria-current="page"
class={cn("text-foreground font-normal", className)}
{...restProps}
>
{@render children?.()}
</span>

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
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,
Ellipsis,
Item,
Separator,
Link,
List,
Page,
//
Root as Breadcrumb,
Ellipsis as BreadcrumbEllipsis,
Item as BreadcrumbItem,
Separator as BreadcrumbSeparator,
Link as BreadcrumbLink,
List as BreadcrumbList,
Page as BreadcrumbPage,
};

View File

@@ -0,0 +1,100 @@
<script lang="ts" module>
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: "ring-offset-background border border-transparent 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-2 focus-visible:ring-offset-2 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",
reverseOutline:
"bg-brand-100 text-primary border-brand-400 hover:text-primary-foreground hover:bg-brand-600 hover:border-brand-500",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border-input bg-background hover:bg-accent hover:text-accent-foreground border",
outlineWhite:
"border-input/50 bg-white hover:bg-accent hover:text-accent-foreground border",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-primary dark:hover:bg-brand-1000/80 hover:text-primary-foreground",
ghostGhosted:
"hover:bg-brand-800/10 text-primary dark:text-primary-foreground dark:hover:bg-brand-100/20",
white: "bg-white text-black hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
success: "bg-success text-success-foreground hover:bg-success/90",
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
},
size: {
default: "px-5 py-3",
sm: "px-3 py-2",
lg: "px-8 py-4 text-base",
icon: "h-12 w-12",
iconSm: "h-10 w-10",
},
shadow: {
default: "",
sm: "shadow-sm",
md: "shadow",
lg: "shadow-lg",
xl: "shadow-xl",
},
},
defaultVariants: {
variant: "default",
size: "default",
shadow: "default",
},
});
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
export type ButtonShadow = VariantProps<typeof buttonVariants>["shadow"];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
shadow?: ButtonShadow;
};
</script>
<script lang="ts">
import { cn } from "$lib/utils.js";
let {
class: className,
variant = "default",
size = "default",
shadow = "default",
ref = $bindable(null),
href = undefined,
type = "button",
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
class={cn(buttonVariants({ variant, size, shadow, className }))}
{href}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
class={cn(buttonVariants({ variant, size, className }))}
{type}
{...restProps}
>
{@render children?.()}
</button>
{/if}

View File

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

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.CellProps = $props();
</script>
<CalendarPrimitive.Cell
bind:ref
class={cn(
"[&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50 relative size-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md",
className
)}
{...restProps}
/>

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
import { Calendar as CalendarPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.DayProps = $props();
</script>
<CalendarPrimitive.Day
bind:ref
class={cn(
buttonVariants({ variant: "ghost" }),
"size-9 p-0 font-normal",
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground",
// Selected
"data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground data-[selected]:opacity-100",
// Disabled
"data-[disabled]:text-muted-foreground data-[disabled]:opacity-50",
// Unavailable
"data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through",
// Outside months
"data-[outside-month]:text-muted-foreground [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground data-[outside-month]:pointer-events-none data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:opacity-30",
className
)}
{...restProps}
/>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridBodyProps = $props();
</script>
<CalendarPrimitive.GridBody bind:ref class={cn(className)} {...restProps} />

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridHeadProps = $props();
</script>
<CalendarPrimitive.GridHead bind:ref class={cn(className)} {...restProps} />

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridRowProps = $props();
</script>
<CalendarPrimitive.GridRow bind:ref class={cn("flex", className)} {...restProps} />

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridProps = $props();
</script>
<CalendarPrimitive.Grid
bind:ref
class={cn("w-full border-collapse space-y-1", className)}
{...restProps}
/>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.HeadCellProps = $props();
</script>
<CalendarPrimitive.HeadCell
bind:ref
class={cn("text-muted-foreground w-9 rounded-md text-[0.8rem] font-normal", className)}
{...restProps}
/>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.HeaderProps = $props();
</script>
<CalendarPrimitive.Header
bind:ref
class={cn("relative flex w-full items-center justify-between pt-1", className)}
{...restProps}
/>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.HeadingProps = $props();
</script>
<CalendarPrimitive.Heading bind:ref class={cn("text-sm font-medium", 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("mt-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import ChevronRight from "@lucide/svelte/icons/chevron-right";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: CalendarPrimitive.PrevButtonProps = $props();
</script>
{#snippet Fallback()}
<ChevronRight class="size-4" />
{/snippet}
<CalendarPrimitive.NextButton
bind:ref
class={cn(
buttonVariants({ variant: "outline" }),
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
className
)}
children={children || Fallback}
{...restProps}
/>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import ChevronLeft from "@lucide/svelte/icons/chevron-left";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: CalendarPrimitive.PrevButtonProps = $props();
</script>
{#snippet Fallback()}
<ChevronLeft class="size-4" />
{/snippet}
<CalendarPrimitive.PrevButton
bind:ref
class={cn(
buttonVariants({ variant: "outline" }),
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
className
)}
children={children || Fallback}
{...restProps}
/>

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import { Calendar as CalendarPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import * as Calendar from "./index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
value = $bindable(),
placeholder = $bindable(),
class: className,
weekdayFormat = "short",
...restProps
}: WithoutChildrenOrChild<CalendarPrimitive.RootProps> = $props();
</script>
<!--
Discriminated Unions + Destructing (required for bindable) do not
get along, so we shut typescript up by casting `value` to `never`.
-->
<CalendarPrimitive.Root
bind:value={value as never}
bind:ref
bind:placeholder
{weekdayFormat}
class={cn("p-3", className)}
{...restProps}
>
{#snippet children({ months, weekdays })}
<Calendar.Header>
<Calendar.PrevButton />
<Calendar.Heading />
<Calendar.NextButton />
</Calendar.Header>
<Calendar.Months>
{#each months as month}
<Calendar.Grid>
<Calendar.GridHead>
<Calendar.GridRow class="flex">
{#each weekdays as weekday}
<Calendar.HeadCell>
{weekday.slice(0, 2)}
</Calendar.HeadCell>
{/each}
</Calendar.GridRow>
</Calendar.GridHead>
<Calendar.GridBody>
{#each month.weeks as weekDates}
<Calendar.GridRow class="mt-2 w-full">
{#each weekDates as date}
<Calendar.Cell {date} month={month.value}>
<Calendar.Day />
</Calendar.Cell>
{/each}
</Calendar.GridRow>
{/each}
</Calendar.GridBody>
</Calendar.Grid>
{/each}
</Calendar.Months>
{/snippet}
</CalendarPrimitive.Root>

View File

@@ -0,0 +1,30 @@
import Root from "./calendar.svelte";
import Cell from "./calendar-cell.svelte";
import Day from "./calendar-day.svelte";
import Grid from "./calendar-grid.svelte";
import Header from "./calendar-header.svelte";
import Months from "./calendar-months.svelte";
import GridRow from "./calendar-grid-row.svelte";
import Heading from "./calendar-heading.svelte";
import GridBody from "./calendar-grid-body.svelte";
import GridHead from "./calendar-grid-head.svelte";
import HeadCell from "./calendar-head-cell.svelte";
import NextButton from "./calendar-next-button.svelte";
import PrevButton from "./calendar-prev-button.svelte";
export {
Day,
Cell,
Grid,
Header,
Months,
GridRow,
Heading,
GridBody,
GridHead,
HeadCell,
NextButton,
PrevButton,
//
Root as Calendar,
};

View File

@@ -0,0 +1,16 @@
<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("p-6", className)} {...restProps}>
{@render children?.()}
</div>

View File

@@ -0,0 +1,16 @@
<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<HTMLParagraphElement>> = $props();
</script>
<p bind:this={ref} class={cn("text-muted-foreground text-sm", className)} {...restProps}>
{@render children?.()}
</p>

View File

@@ -0,0 +1,16 @@
<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 items-center p-6 pt-0", className)} {...restProps}>
{@render children?.()}
</div>

View File

@@ -0,0 +1,16 @@
<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 space-y-1.5 p-6 pb-0", className)} {...restProps}>
{@render children?.()}
</div>

View File

@@ -0,0 +1,25 @@
<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,
level = 3,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
level?: 1 | 2 | 3 | 4 | 5 | 6;
} = $props();
</script>
<div
role="heading"
aria-level={level}
bind:this={ref}
class={cn("text-2xl font-semibold leading-none tracking-tight", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,23 @@
<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(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className,
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,22 @@
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,
Content,
Description,
Footer,
Header,
Title,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
};

View File

@@ -0,0 +1,13 @@
<script lang="ts">
</script>
<div
class="flex h-[200px] w-full items-end gap-2 overflow-hidden rounded-lg border bg-background p-6"
>
{#each Array(12) as _, i}
<div
class="flex-1 animate-pulse rounded-md bg-primary/10"
style="height: {30 + Math.random() * 70}%"
></div>
{/each}
</div>

View File

@@ -0,0 +1,135 @@
<script lang="ts">
import { onMount } from "svelte";
import type { Chart as ChartType, ChartOptions } from "chart.js";
let {
data = [],
index = "name",
categories = ["value"],
colors = ["primary"],
valueFormatter = (value: any) => value.toString(),
showLegend = true,
}: {
data: any[];
index?: string;
categories?: string[];
colors?: string[];
valueFormatter?: (value: any) => string;
showLegend?: boolean;
} = $props();
let chartElement: HTMLCanvasElement | undefined = $state(undefined);
let chart: ChartType | undefined = $state(undefined);
const colorMap: Record<string, string> = {
primary: "#7e67e6",
blue: "#3b82f6",
green: "#22c55e",
emerald: "#10b981",
rose: "#f43f5e",
amber: "#f59e0b",
};
onMount(() => {
if (typeof window !== "undefined" && data.length > 0) {
// Modern ES Module import with explicit imports
Promise.all([import("chart.js/auto"), import("chart.js")]).then(
([
ChartModule,
{
Chart,
BarController,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
},
]) => {
// Register only what we need
Chart.register(
BarController,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
);
if (chartElement) {
const labels = data.map((item) => item[index]);
const datasets = categories.map((category, i) => ({
label: category,
data: data.map((item) => item[category] || 0),
backgroundColor: colorMap[colors[i % colors.length]],
borderRadius: 4,
}));
chart = new Chart(chartElement, {
type: "bar",
data: {
labels,
datasets,
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: showLegend,
position: "top",
},
tooltip: {
callbacks: {
label: function (context) {
return valueFormatter(
context.raw,
);
},
},
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return value;
},
},
},
},
} as ChartOptions,
});
}
},
);
}
return () => {
if (chart) {
chart.destroy();
}
};
});
$effect(() => {
if (chart && data.length > 0) {
chart.data.labels = data.map((item) => item[index]);
categories.forEach((category, i) => {
if (chart?.data?.datasets?.[i]) {
chart.data.datasets[i].data = data.map(
(item) => item[category] || 0,
);
}
});
chart.update();
}
});
</script>
<div class="h-[300px] w-full">
<canvas bind:this={chartElement}></canvas>
</div>

View File

@@ -0,0 +1,6 @@
<script lang="ts">
</script>
<div class="w-full">
<slot />
</div>

View File

@@ -0,0 +1,6 @@
<script lang="ts">
</script>
<p class="mb-4 text-sm text-muted-foreground">
<slot />
</p>

View File

@@ -0,0 +1,6 @@
<script lang="ts">
</script>
<h4 class="mb-1 text-sm font-medium text-foreground">
<slot />
</h4>

View File

@@ -0,0 +1,6 @@
<script lang="ts">
</script>
<h3 class="text-lg font-semibold tracking-tight">
<slot />
</h3>

View File

@@ -0,0 +1,15 @@
import BarChart from "./bar-chart.svelte";
import BarChartSkeleton from "./bar-chart-skeleton.svelte";
import ChartContainer from "./chart-container.svelte";
import Title from "./chart-title.svelte";
import Description from "./chart-description.svelte";
import Heading from "./chart-heading.svelte";
export {
BarChart,
BarChartSkeleton,
ChartContainer,
Title,
Description,
Heading,
};

View File

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

View File

@@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox,
};

View File

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

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive, 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),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
children: childrenProp,
...restProps
}: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & {
children?: Snippet;
} = $props();
</script>
<ContextMenuPrimitive.CheckboxItem
bind:ref
bind:checked
bind:indeterminate
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",
className
)}
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<Minus class="size-3.5" />
{:else}
<Check class={cn("size-3.5", !checked && "text-transparent")} />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</ContextMenuPrimitive.CheckboxItem>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive, type WithoutChild } from "bits-ui";
import Circle from "@lucide/svelte/icons/circle";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children: childrenProp,
...restProps
}: WithoutChild<ContextMenuPrimitive.RadioItemProps> = $props();
</script>
<ContextMenuPrimitive.RadioItem
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",
className
)}
{...restProps}
>
{#snippet children({ checked })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if checked}
<Circle class="size-2 fill-current" />
{/if}
</span>
{@render childrenProp?.({ checked })}
{/snippet}
</ContextMenuPrimitive.RadioItem>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: ContextMenuPrimitive.SeparatorProps = $props();
</script>
<ContextMenuPrimitive.Separator
bind:ref
class={cn("bg-border -mx-1 my-1 h-px", 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<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...restProps}
>
{@render children?.()}
</span>

View File

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

View File

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

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