This commit is contained in:
Omer Sabic 2024-03-30 15:42:01 +01:00
parent f31d6e9e65
commit cc0cb11879
68 changed files with 1842 additions and 34 deletions

84
package-lock.json generated
View File

@ -10,8 +10,10 @@
"dependencies": { "dependencies": {
"bits-ui": "^0.21.1", "bits-ui": "^0.21.1",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"cmdk-sv": "^0.0.16",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"drizzle-orm": "^0.30.4", "drizzle-orm": "^0.30.4",
"lucide-svelte": "^0.363.0",
"pg": "^8.11.3", "pg": "^8.11.3",
"tailwind-merge": "^2.2.2", "tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1" "tailwind-variants": "^0.2.1"
@ -1714,6 +1716,80 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cmdk-sv": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/cmdk-sv/-/cmdk-sv-0.0.16.tgz",
"integrity": "sha512-erlrIrRPaPgiiCFYZgWxTbqgfaLKjiwAC4tKNJH4eUpMBilGqEe2Ruiip9oYakWcZM14JQhcuiYqOl9ee4KI1Q==",
"dependencies": {
"bits-ui": "^0.9.0",
"nanoid": "^5.0.2"
},
"peerDependencies": {
"svelte": "^4.0.0"
}
},
"node_modules/cmdk-sv/node_modules/@melt-ui/svelte": {
"version": "0.61.2",
"resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.61.2.tgz",
"integrity": "sha512-BHkD9G31zQBToA4euDRBgTQRvWxT9scufOVCXgDO6HKTvyxFspbWT2bgiSFqAK4BbAGDn9Ao36Q8F9O71KN4OQ==",
"dependencies": {
"@floating-ui/core": "^1.3.1",
"@floating-ui/dom": "^1.4.5",
"@internationalized/date": "^3.5.0",
"dequal": "^2.0.3",
"focus-trap": "^7.5.2",
"nanoid": "^4.0.2"
},
"peerDependencies": {
"svelte": ">=3 <5"
}
},
"node_modules/cmdk-sv/node_modules/@melt-ui/svelte/node_modules/nanoid": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
"integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^14 || ^16 || >=18"
}
},
"node_modules/cmdk-sv/node_modules/bits-ui": {
"version": "0.9.9",
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.9.9.tgz",
"integrity": "sha512-LkdkyTtpXdkjBzPZJVJgpcre4fut6DONoprMfadHFo82HNUhph+02CxDjYEcZcThb5z4YjSxMlCYvQPZm+YtfQ==",
"dependencies": {
"@melt-ui/svelte": "0.61.2",
"nanoid": "^5.0.3"
},
"peerDependencies": {
"svelte": "^4.0.0"
}
},
"node_modules/cmdk-sv/node_modules/nanoid": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz",
"integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/code-red": { "node_modules/code-red": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
@ -3123,6 +3199,14 @@
"es5-ext": "~0.10.2" "es5-ext": "~0.10.2"
} }
}, },
"node_modules/lucide-svelte": {
"version": "0.363.0",
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.363.0.tgz",
"integrity": "sha512-zpUBFtMEEOOjILgiDX48ijibUww3JUCLrMo5YDGX/De/m6I+vn+oWIGvdyZtuc8nz/P8xHW9vWLKzIWeMrRYbA==",
"peerDependencies": {
"svelte": "^3 || ^4 || ^5.0.0-next.42"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.8", "version": "0.30.8",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",

View File

@ -29,8 +29,10 @@
"dependencies": { "dependencies": {
"bits-ui": "^0.21.1", "bits-ui": "^0.21.1",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"cmdk-sv": "^0.0.16",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"drizzle-orm": "^0.30.4", "drizzle-orm": "^0.30.4",
"lucide-svelte": "^0.363.0",
"pg": "^8.11.3", "pg": "^8.11.3",
"tailwind-merge": "^2.2.2", "tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1" "tailwind-variants": "^0.2.1"

130
src/lib/admin/nav.svelte Normal file
View File

@ -0,0 +1,130 @@
<script lang="ts">
import LineChart from "lucide-svelte/icons/line-chart";
import Menu from "lucide-svelte/icons/menu";
import Package2 from "lucide-svelte/icons/package-2";
import Users from "lucide-svelte/icons/users";
import { Badge } from "$lib/components/ui/badge/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import * as Sheet from "$lib/components/ui/sheet/index.js";
import { Book } from "lucide-svelte";
export let title = "Example title";
function isSelected(buttonText: string) {
return title.toLowerCase() === buttonText.toLowerCase() ? "bg-muted" : "text-muted-foreground text-primary";
}
</script>
<div
class="grid h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]"
>
<div class="hidden border-r bg-muted/40 md:block">
<div class="flex h-full max-h-screen flex-col gap-2">
<div
class="flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6"
>
<a href="/" class="flex items-center gap-2 font-semibold">
<Package2 class="h-6 w-6" />
<span class="">Acme Inc</span>
</a>
<!-- <Button variant="outline" size="icon" class="ml-auto h-8 w-8">
<Bell class="h-4 w-4" />
<span class="sr-only">Toggle notifications</span>
</Button> -->
</div>
<div class="flex-1">
<nav class="grid items-start px-2 text-sm font-medium lg:px-4">
<a
href="##"
class="flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary {isSelected("activity")}"
>
<LineChart class="h-4 w-4" />
Activity
<Badge
class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
>
6
</Badge>
</a>
<a
href="/admin/users"
class="flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary {isSelected("users")}"
>
<Users class="h-4 w-4" />
Users
</a>
<a
href="##"
class="flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary {isSelected("manga")}"
>
<Book class="h-4 w-4" />
Manga
</a>
</nav>
</div>
</div>
</div>
<div class="flex flex-col">
<header
class="flex h-14 items-center gap-4 border-b bg-muted/40 px-4 lg:h-[60px] lg:px-6"
>
<Sheet.Root>
<Sheet.Trigger asChild let:builder>
<Button
variant="outline"
size="icon"
class="shrink-0 md:hidden"
builders={[builder]}
>
<Menu class="h-5 w-5" />
<span class="sr-only">Toggle navigation menu</span>
</Button>
</Sheet.Trigger>
<Sheet.Content side="left" class="flex flex-col">
<nav class="grid gap-2 text-lg font-medium">
<a
href="##"
class="flex items-center gap-2 text-lg font-semibold"
>
<Package2 class="h-6 w-6" />
<span class="sr-only">Acme Inc</span>
</a>
<a
href="##"
class="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
<LineChart class="h-5 w-5" />
Activity
</a>
<a
href="/admin/users"
class="mx-[-0.65rem] flex items-center gap-4 rounded-xl bg-muted px-3 py-2 text-foreground hover:text-foreground"
>
<Users class="h-5 w-5" />
Users
<Badge
class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
>
6
</Badge>
</a>
<a
href="##"
class="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
<Book class="h-5 w-5" />
Manga
</a>
</nav>
</Sheet.Content>
</Sheet.Root>
</header>
<main class="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6">
<div class="flex items-center">
<h1 class="text-lg font-semibold md:text-2xl">{title}</h1>
</div>
<slot />
</main>
</div>
</div>

View File

@ -0,0 +1,68 @@
<script>
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import * as Select from "$lib/components/ui/select";
import * as Command from "$lib/components/ui/command";
import { ChevronDown } from "lucide-svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
const roles = ["Member", "Admin"];
/** @type {"Member" | "Admin"} */
export let selected = "Member";
/** @param {"Member" | "Admin"} value */
function select(value) {
console.log(value);
selected = value;
dispatch("select", value);
}
$: selected;
</script>
<!-- <Popover.Root>
<Popover.Trigger asChild let:builder>
<Button builders={[builder]} variant="outline" class="ml-auto">
{selected}
<ChevronDown class="ml-2 h-4 w-4 text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="p-0" align="end">
<Command.Root>
<Command.List>
<Command.Group>
<Command.Item class="flex flex-col items-start space-y-1 px-4 py-2 cursor-pointer" on:click={() => select("Member")}>
<p>Member</p>
<p class="text-sm text-muted-foreground">
Can read manga and submit updates.
</p>
</Command.Item>
<Command.Item class="flex flex-col items-start space-y-1 px-4 py-2 cursor-pointer" on:click={() => select("Admin")}>
<p>Admin</p>
<p class="text-sm text-muted-foreground">
Can read, edit, delete and add users, manga and activity.
</p>
</Command.Item>
</Command.Group>
</Command.List>
</Command.Root>
</Popover.Content>
</Popover.Root> -->
<Select.Root>
<Select.Trigger class="w-[180px]">
<Select.Value placeholder="Select a role" />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Roles</Select.Label>
{#each roles as role}
<Select.Item value={role} label={role}>{role}</Select.Item>
{/each}
</Select.Group>
</Select.Content>
<Select.Input name="userRole" />
</Select.Root>

View File

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

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AvatarPrimitive.ImageProps;
let className: $$Props["class"] = undefined;
export let src: $$Props["src"] = undefined;
export let alt: $$Props["alt"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Image
{src}
{alt}
class={cn("aspect-square h-full w-full", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AvatarPrimitive.Props;
let className: $$Props["class"] = undefined;
export let delayMs: $$Props["delayMs"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Root
{delayMs}
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Root>

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,18 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import { badgeVariants, type Variant } from "./index.js";
let className: string | undefined | null = undefined;
export let href: string | undefined = undefined;
export let variant: Variant = "default";
export { className as class };
</script>
<svelte:element
this={href ? "a" : "span"}
{href}
class={cn(badgeVariants({ variant, className }))}
{...$$restProps}
>
<slot />
</svelte:element>

View File

@ -0,0 +1,21 @@
import { tv, type VariantProps } from "tailwind-variants";
export { default as Badge } from "./badge.svelte";
export const badgeVariants = tv({
base: "inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none select-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
variants: {
variant: {
default: "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
secondary:
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
destructive:
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
});
export type Variant = VariantProps<typeof badgeVariants>["variant"];

View File

@ -0,0 +1,23 @@
<script lang="ts">
import Command from "./command.svelte";
import * as Dialog from "$lib/components/ui/dialog/index.js";
import type { Dialog as DialogPrimitive } from "bits-ui";
import type { Command as CommandPrimitive } from "cmdk-sv";
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps;
export let open: $$Props["open"] = false;
export let value: $$Props["value"] = undefined;
</script>
<Dialog.Root bind:open {...$$restProps}>
<Dialog.Content class="overflow-hidden p-0 shadow-lg">
<Command
class="[&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group]:not([hidden])_~[data-cmdk-group]]:pt-0 [&_[data-cmdk-group]]:px-2 [&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5 [&_[data-cmdk-input]]:h-12 [&_[data-cmdk-item]]:px-2 [&_[data-cmdk-item]]:py-3 [&_[data-cmdk-item]_svg]:h-5 [&_[data-cmdk-item]_svg]:w-5"
{...$$restProps}
bind:value
>
<slot />
</Command>
</Dialog.Content>
</Dialog.Root>

View File

@ -0,0 +1,12 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.EmptyProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Empty class={cn("py-6 text-center text-sm", className)} {...$$restProps}>
<slot />
</CommandPrimitive.Empty>

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.GroupProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Group
class={cn(
"overflow-hidden p-1 text-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group-heading]]:text-muted-foreground",
className
)}
{...$$restProps}
>
<slot />
</CommandPrimitive.Group>

View File

@ -0,0 +1,23 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import Search from "lucide-svelte/icons/search";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.InputProps;
let className: string | undefined | null = undefined;
export { className as class };
export let value: string = "";
</script>
<div class="flex items-center border-b px-2" data-cmdk-input-wrapper="">
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
class={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...$$restProps}
bind:value
/>
</div>

View File

@ -0,0 +1,24 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.ItemProps;
export let asChild = false;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Item
{asChild}
class={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...$$restProps}
let:action
let:attrs
>
<slot {action} {attrs} />
</CommandPrimitive.Item>

View File

@ -0,0 +1,15 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.ListProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.List
class={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...$$restProps}
>
<slot />
</CommandPrimitive.List>

View File

@ -0,0 +1,10 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.SeparatorProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Separator class={cn("-mx-1 h-px bg-border", className)} {...$$restProps} />

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<span
class={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</span>

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.CommandProps;
export let value: $$Props["value"] = undefined;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Root
class={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
bind:value
{...$$restProps}
>
<slot />
</CommandPrimitive.Root>

View File

@ -0,0 +1,37 @@
import { Command as CommandPrimitive } from "cmdk-sv";
import Root from "./command.svelte";
import Dialog from "./command-dialog.svelte";
import Empty from "./command-empty.svelte";
import Group from "./command-group.svelte";
import Item from "./command-item.svelte";
import Input from "./command-input.svelte";
import List from "./command-list.svelte";
import Separator from "./command-separator.svelte";
import Shortcut from "./command-shortcut.svelte";
const Loading = CommandPrimitive.Loading;
export {
Root,
Dialog,
Empty,
Group,
Item,
Input,
List,
Separator,
Shortcut,
Loading,
//
Root as Command,
Dialog as CommandDialog,
Empty as CommandEmpty,
Group as CommandGroup,
Item as CommandItem,
Input as CommandInput,
List as CommandList,
Separator as CommandSeparator,
Shortcut as CommandShortcut,
Loading as CommandLoading,
};

View File

@ -0,0 +1,36 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import X from "lucide-svelte/icons/x";
import * as Dialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 200,
};
export { className as class };
</script>
<Dialog.Portal>
<Dialog.Overlay />
<DialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
<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="h-4 w-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";
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Description>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { fade } from "svelte/transition";
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,8 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
type $$Props = DialogPrimitive.PortalProps;
</script>
<DialogPrimitive.Portal {...$$restProps}>
<slot />
</DialogPrimitive.Portal>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Title
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Title>

View File

@ -0,0 +1,34 @@
import { Dialog as DialogPrimitive } from "bits-ui";
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.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";
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
//
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,
};

View File

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
let className: $$Props["class"] = undefined;
export let checked: $$Props["checked"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.CheckboxItem
bind:checked
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.CheckboxIndicator>
<Check class="h-4 w-4" />
</DropdownMenuPrimitive.CheckboxIndicator>
</span>
<slot />
</DropdownMenuPrimitive.CheckboxItem>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.ContentProps;
type $$Events = DropdownMenuPrimitive.ContentEvents;
let className: $$Props["class"] = undefined;
export let sideOffset: $$Props["sideOffset"] = 4;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
className
)}
{...$$restProps}
on:keydown
>
<slot />
</DropdownMenuPrimitive.Content>

View File

@ -0,0 +1,31 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.ItemEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Item
class={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<slot />
</DropdownMenuPrimitive.Item>

View File

@ -0,0 +1,19 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.LabelProps & {
inset?: boolean;
};
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Label
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...$$restProps}
>
<slot />
</DropdownMenuPrimitive.Label>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
export let value: $$Props["value"] = undefined;
</script>
<DropdownMenuPrimitive.RadioGroup {...$$restProps} bind:value>
<slot />
</DropdownMenuPrimitive.RadioGroup>

View File

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Circle from "lucide-svelte/icons/circle";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"];
export { className as class };
</script>
<DropdownMenuPrimitive.RadioItem
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{value}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.RadioIndicator>
<Circle class="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.RadioIndicator>
</span>
<slot />
</DropdownMenuPrimitive.RadioItem>

View File

@ -0,0 +1,14 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SeparatorProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Separator
class={cn("-mx-1 my-1 h-px bg-muted", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
<slot />
</span>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SubContentProps;
type $$Events = DropdownMenuPrimitive.SubContentEvents;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
x: -10,
y: 0,
};
export { className as class };
</script>
<DropdownMenuPrimitive.SubContent
{transition}
{transitionConfig}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
className
)}
{...$$restProps}
on:keydown
on:focusout
on:pointermove
>
<slot />
</DropdownMenuPrimitive.SubContent>

View File

@ -0,0 +1,32 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.SubTriggerEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.SubTrigger
class={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
>
<slot />
<ChevronRight class="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>

View File

@ -0,0 +1,48 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Item from "./dropdown-menu-item.svelte";
import Label from "./dropdown-menu-label.svelte";
import Content from "./dropdown-menu-content.svelte";
import Shortcut from "./dropdown-menu-shortcut.svelte";
import RadioItem from "./dropdown-menu-radio-item.svelte";
import Separator from "./dropdown-menu-separator.svelte";
import RadioGroup from "./dropdown-menu-radio-group.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
const Trigger = DropdownMenuPrimitive.Trigger;
const Group = DropdownMenuPrimitive.Group;
export {
Sub,
Root,
Item,
Label,
Group,
Trigger,
Content,
Shortcut,
Separator,
RadioItem,
SubContent,
SubTrigger,
RadioGroup,
CheckboxItem,
//
Root as DropdownMenu,
Sub as DropdownMenuSub,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
Group as DropdownMenuGroup,
Content as DropdownMenuContent,
Trigger as DropdownMenuTrigger,
Shortcut as DropdownMenuShortcut,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
RadioGroup as DropdownMenuRadioGroup,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
CheckboxItem as DropdownMenuCheckboxItem,
};

View File

@ -0,0 +1,27 @@
import Root from "./input.svelte";
export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
};
export {
Root,
//
Root as Input,
};

View File

@ -0,0 +1,35 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
import type { InputEvents } from "./index.js";
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
</script>
<input
class={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:value
on:blur
on:change
on:click
on:focus
on:focusin
on:focusout
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:input
{...$$restProps}
/>

View File

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

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { Label as LabelPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = LabelPrimitive.Props;
type $$Events = LabelPrimitive.Events;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<LabelPrimitive.Root
class={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...$$restProps}
on:mousedown
>
<slot />
</LabelPrimitive.Root>

View File

@ -0,0 +1,17 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = PopoverPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<PopoverPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
className
)}
{...$$restProps}
>
<slot />
</PopoverPrimitive.Content>

View File

@ -0,0 +1,34 @@
import { Select as SelectPrimitive } from "bits-ui";
import Label from "./select-label.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";
const Root = SelectPrimitive.Root;
const Group = SelectPrimitive.Group;
const Input = SelectPrimitive.Input;
const Value = SelectPrimitive.Value;
export {
Root,
Group,
Input,
Label,
Item,
Value,
Content,
Trigger,
Separator,
//
Root as Select,
Group as SelectGroup,
Input as SelectInput,
Label as SelectLabel,
Item as SelectItem,
Value as SelectValue,
Content as SelectContent,
Trigger as SelectTrigger,
Separator as SelectSeparator,
};

View File

@ -0,0 +1,39 @@
<script lang="ts">
import { Select as SelectPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
import { scale } from "svelte/transition";
type $$Props = SelectPrimitive.ContentProps;
type $$Events = SelectPrimitive.ContentEvents;
export let sideOffset: $$Props["sideOffset"] = 4;
export let inTransition: $$Props["inTransition"] = flyAndScale;
export let inTransitionConfig: $$Props["inTransitionConfig"] = undefined;
export let outTransition: $$Props["outTransition"] = scale;
export let outTransitionConfig: $$Props["outTransitionConfig"] = {
start: 0.95,
opacity: 0,
duration: 50,
};
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SelectPrimitive.Content
{inTransition}
{inTransitionConfig}
{outTransition}
{outTransitionConfig}
{sideOffset}
class={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md outline-none",
className
)}
{...$$restProps}
on:keydown
>
<div class="w-full p-1">
<slot />
</div>
</SelectPrimitive.Content>

View File

@ -0,0 +1,40 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import Check from "lucide-svelte/icons/check";
import { Select as SelectPrimitive } from "bits-ui";
type $$Props = SelectPrimitive.ItemProps;
type $$Events = SelectPrimitive.ItemEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"];
export let label: $$Props["label"] = undefined;
export let disabled: $$Props["disabled"] = undefined;
export { className as class };
</script>
<SelectPrimitive.Item
{value}
{disabled}
{label}
class={cn(
"relative flex w-full 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}
on:click
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check class="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<slot>
{label ? label : value}
</slot>
</SelectPrimitive.Item>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { Select as SelectPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = SelectPrimitive.LabelProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SelectPrimitive.Label
class={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...$$restProps}
>
<slot />
</SelectPrimitive.Label>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import { Select as SelectPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = SelectPrimitive.SeparatorProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SelectPrimitive.Separator class={cn("-mx-1 my-1 h-px bg-muted", className)} {...$$restProps} />

View File

@ -0,0 +1,27 @@
<script lang="ts">
import { Select as SelectPrimitive } from "bits-ui";
import ChevronDown from "lucide-svelte/icons/chevron-down";
import { cn } from "$lib/utils.js";
type $$Props = SelectPrimitive.TriggerProps;
type $$Events = SelectPrimitive.TriggerEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SelectPrimitive.Trigger
class={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...$$restProps}
let:builder
on:click
on:keydown
>
<slot {builder} />
<div>
<ChevronDown class="h-4 w-4 opacity-50" />
</div>
</SelectPrimitive.Trigger>

View File

@ -0,0 +1,106 @@
import { Dialog as SheetPrimitive } from "bits-ui";
import { tv, type VariantProps } from "tailwind-variants";
import Portal from "./sheet-portal.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;
const Trigger = SheetPrimitive.Trigger;
export {
Root,
Close,
Trigger,
Portal,
Overlay,
Content,
Header,
Footer,
Title,
Description,
//
Root as Sheet,
Close as SheetClose,
Trigger as SheetTrigger,
Portal as SheetPortal,
Overlay as SheetOverlay,
Content as SheetContent,
Header as SheetHeader,
Footer as SheetFooter,
Title as SheetTitle,
Description as SheetDescription,
};
export const sheetVariants = tv({
base: "fixed z-50 gap-4 bg-background p-6 shadow-lg",
variants: {
side: {
top: "inset-x-0 top-0 border-b",
bottom: "inset-x-0 bottom-0 border-t",
left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
right: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
});
export const sheetTransitions = {
top: {
in: {
y: "-100%",
duration: 500,
opacity: 1,
},
out: {
y: "-100%",
duration: 300,
opacity: 1,
},
},
bottom: {
in: {
y: "100%",
duration: 500,
opacity: 1,
},
out: {
y: "100%",
duration: 300,
opacity: 1,
},
},
left: {
in: {
x: "-100%",
duration: 500,
opacity: 1,
},
out: {
x: "-100%",
duration: 300,
opacity: 1,
},
},
right: {
in: {
x: "100%",
duration: 500,
opacity: 1,
},
out: {
x: "100%",
duration: 300,
opacity: 1,
},
},
};
export type Side = VariantProps<typeof sheetVariants>["side"];

View File

@ -0,0 +1,47 @@
<script lang="ts">
import { Dialog as SheetPrimitive } from "bits-ui";
import X from "lucide-svelte/icons/x";
import {
SheetOverlay,
SheetPortal,
sheetTransitions,
sheetVariants,
type Side,
} from "./index.js";
import { cn } from "$lib/utils.js";
import { fly } from "svelte/transition";
type $$Props = SheetPrimitive.ContentProps & {
side?: Side;
};
let className: $$Props["class"] = undefined;
export let side: $$Props["side"] = "right";
export { className as class };
export let inTransition: $$Props["inTransition"] = fly;
export let inTransitionConfig: $$Props["inTransitionConfig"] =
sheetTransitions[side ? side : "right"]["in"];
export let outTransition: $$Props["outTransition"] = fly;
export let outTransitionConfig: $$Props["outTransitionConfig"] =
sheetTransitions[side ? side : "right"]["out"];
</script>
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
{inTransition}
{inTransitionConfig}
{outTransition}
{outTransitionConfig}
class={cn(sheetVariants({ side }), className)}
{...$$restProps}
>
<slot />
<SheetPrimitive.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-secondary"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = SheetPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SheetPrimitive.Description class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
<slot />
</SheetPrimitive.Description>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { fade } from "svelte/transition";
type $$Props = SheetPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<SheetPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm ", className)}
{...$$restProps}
/>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = SheetPrimitive.PortalProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SheetPrimitive.Portal class={cn(className)} {...$$restProps}>
<slot />
</SheetPrimitive.Portal>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = SheetPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<SheetPrimitive.Title
class={cn("text-lg font-semibold text-foreground", className)}
{...$$restProps}
>
<slot />
</SheetPrimitive.Title>

View File

@ -0,0 +1,45 @@
<script lang="ts">
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import * as Avatar from "$lib/components/ui/avatar";
import { Button } from "$lib/components/ui/button";
import { userStore } from "$lib/state";
type UserData = {
id: string,
username: string
}
export let user: UserData;
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button variant="ghost" builders={[builder]} class="relative h-8 w-8 rounded-full">
<Avatar.Root class="h-8 w-8">
<!-- <Avatar.Image src="/avatars/01.png" alt="@{user.username}" /> -->
<Avatar.Fallback>{user.username?.substring(0,2)}</Avatar.Fallback>
</Avatar.Root>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56" align="end">
<DropdownMenu.Label class="font-normal">
<div class="flex flex-col space-y-1">
<p class="text-sm font-medium leading-none">@{user.username}</p>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item href="/profile">
Profile
<DropdownMenu.Shortcut>⇧⌘P</DropdownMenu.Shortcut>
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item on:click={() => {
userStore.set(null);
}}>
Log out
<DropdownMenu.Shortcut>⇧⌘Q</DropdownMenu.Shortcut>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>

19
src/lib/state.js Normal file
View File

@ -0,0 +1,19 @@
import { browser } from '$app/environment';
import { writable } from 'svelte/store'
let stored = null;
if(browser && localStorage.auth) {
stored = JSON.parse(localStorage.auth);
}
/**
* @type {import('svelte/store').Writable<{id: string, username: string} | null>}
*/
export const userStore = writable(stored || null)
userStore.subscribe((value) => {
if(browser) {
localStorage.auth = JSON.stringify(value)
}
})

View File

@ -0,0 +1,57 @@
<script>
import UserNav from "$lib/components/user-nav.svelte";
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import { Label } from "$lib/components/ui/label";
import { Input } from "$lib/components/ui/input";
import { userStore } from "$lib/state";
// const user = {
// id: "helloworld",
// username: "Jar Allerton",
// };
let tokenInput = "";
function logIn() {
userStore.set({
id: tokenInput,
username: "joe"
})
}
</script>
<div class="max-w-6xl mx-auto">
<div class="h-16 flex items-center px-4">
<Button>Start new manga</Button>
<div class="ml-auto">
{#if $userStore !== null}
<UserNav user={$userStore} />
{:else}
<Popover.Root>
<Popover.Trigger asChild let:builder>
<Button builders={[builder]} variant="outline"
>Log in</Button
>
</Popover.Trigger>
<Popover.Content class="w-80">
<div class="grid gap-4">
<div class="grid gap-2">
<div
class="grid grid-cols-3 items-center gap-4"
>
<Label for="width">Auth token</Label>
<Input
id="width"
class="col-span-2 h-8"
bind:value={tokenInput}
/>
</div>
<Button on:click={logIn}>Log in</Button>
</div>
</div>
</Popover.Content>
</Popover.Root>
{/if}
</div>
</div>
<slot />
</div>

View File

@ -0,0 +1,36 @@
<script>
import { ScrollArea } from "$lib/components/ui/scroll-area";
import * as Card from "$lib/components/ui/card";
import { Button } from "$lib/components/ui/button";
const mangas = [
{
name: "Solo Leveling",
type: "Manhua",
},
];
</script>
<div>
<h2 class="text-2xl font-bold margin-y-4">Currently reading</h2>
<ScrollArea
class="w-full whitespace-nowrap border-y"
orientation="horizontal"
>
<div class="flex w-max space-x-4 p-4">
{#each mangas as manga}
<Card.Root>
<Card.Header>
<Card.Title>{manga.name}</Card.Title>
</Card.Header>
<Card.Content>
<p>Card Content</p>
</Card.Content>
<Card.Footer>
<Button>Complete chapter 10-20</Button>
</Card.Footer>
</Card.Root>
{/each}
</div>
</ScrollArea>
</div>

View File

@ -1 +1,5 @@
<script>import "../app.pcss";</script><slot></slot> <script>
import "../app.pcss"
</script>
<slot />

View File

@ -1,33 +0,0 @@
<script>
import { ScrollArea } from "$lib/components/ui/scroll-area";
import * as Card from "$lib/components/ui/card";
import { Button } from "$lib/components/ui/button";
const mangas = [
{
name: "Solo Leveling",
type: "Manhua"
}
]
</script>
<ScrollArea
class="w-96 whitespace-nowrap rounded-md border"
orientation="horizontal"
>
<div class="flex w-max space-x-4 p-4">
{#each mangas as manga}
<Card.Root>
<Card.Header>
<Card.Title>{manga.name}</Card.Title>
</Card.Header>
<Card.Content>
<p>Card Content</p>
</Card.Content>
<Card.Footer>
<Button>Read</Button>
</Card.Footer>
</Card.Root>
{/each}
</div>
</ScrollArea>

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import Nav from "$lib/admin/nav.svelte";
</script>
<Nav title="Inventory">
<div
class="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm"
>
<div class="flex flex-col items-center gap-1 text-center">
<h3 class="text-2xl font-bold tracking-tight">
You have no products
</h3>
<p class="text-sm text-muted-foreground">
You can start selling as soon as you add a product.
</p>
<Button class="mt-4">Add Product</Button>
</div>
</div>
</Nav>

View File

@ -0,0 +1,7 @@
import type { Actions } from "@sveltejs/kit";
export const actions: Actions = {
default: async (event) => {
console.log(event.request.body);
},
};

View File

@ -0,0 +1,69 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import Nav from "$lib/admin/nav.svelte";
import * as Avatar from "$lib/components/ui/avatar";
import UserRoleDropdown from "$lib/admin/user-role-dropdown.svelte";
import * as Dialog from "$lib/components/ui/dialog";
import { Label } from "$lib/components/ui/label";
import {Input} from "$lib/components/ui/input";
const users: { username: string; role: "Member" | "Admin"; }[] = [
{
username: "Joe Biden",
role: "Member",
},
{
username: "BoeJiden",
role: "Admin",
},
];
</script>
<Nav title="Users">
<Dialog.Root>
<Dialog.Trigger class="w-12">
<Button>
Add User
</Button>
</Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>Create user</Dialog.Title>
</Dialog.Header>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">Username</Label>
<Input id="name" value="Pedro Duarte" class="col-span-3" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right">Role</Label>
<UserRoleDropdown />
</div>
</div>
<Dialog.Footer>
<Button type="submit">Save changes</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<div class="grid gap-6 max-w-2xl">
{#each users as user}
<div class="flex items-center justify-between space-x-4">
<div class="flex items-center space-x-4">
<Avatar.Root>
<!-- <Avatar.Image src="/avatars/01.png" alt="Sofia Davis" /> -->
<Avatar.Fallback
>{user.username.substring(0, 2)}</Avatar.Fallback
>
</Avatar.Root>
<div>
<p class="text-sm font-medium leading-none">
{user.username}
</p>
</div>
</div>
<UserRoleDropdown selected={user.role} />
</div>
{/each}
</div>
</Nav>