feat: additional component and styling choices

This commit is contained in:
Bart van der Braak 2024-01-29 00:55:20 +01:00
parent 88884a69ac
commit b03dcbaf2d
52 changed files with 1077 additions and 78 deletions

View file

@ -38,6 +38,8 @@
"dependencies": {
"bits-ui": "^0.15.1",
"clsx": "^2.1.0",
"lucide-svelte": "^0.316.0",
"mode-watcher": "^0.1.2",
"pocketbase": "^0.21.0",
"radix-icons-svelte": "^1.2.1",
"tailwind-merge": "^2.2.1",

View file

@ -11,6 +11,12 @@ dependencies:
clsx:
specifier: ^2.1.0
version: 2.1.0
lucide-svelte:
specifier: ^0.316.0
version: 0.316.0(svelte@4.2.9)
mode-watcher:
specifier: ^0.1.2
version: 0.1.2(svelte@4.2.9)
pocketbase:
specifier: ^0.21.0
version: 0.21.0
@ -1651,6 +1657,14 @@ packages:
yallist: 4.0.0
dev: true
/lucide-svelte@0.316.0(svelte@4.2.9):
resolution: {integrity: sha512-6mtBw/aU1IIVXfN6AXLSwsbeCFX/2/tTy84rrDTDsOq7BEijFbvObQsPEWPxJ0EyCjUXOYfAvfkZzMosYjyGqA==}
peerDependencies:
svelte: '>=3 <5'
dependencies:
svelte: 4.2.9
dev: false
/magic-string@0.30.5:
resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
engines: {node: '>=12'}
@ -1703,6 +1717,14 @@ packages:
minimist: 1.2.8
dev: true
/mode-watcher@0.1.2(svelte@4.2.9):
resolution: {integrity: sha512-XTdPCdqC3kqSvB+Q262Kor983YJkkB2Z3vj9uqg5IqKQpOdiz+xB99Jihp8sWbyM67drC7KKp0Nt5FzCypZi2g==}
peerDependencies:
svelte: ^4.0.0
dependencies:
svelte: 4.2.9
dev: false
/mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}

View file

@ -2,11 +2,23 @@
<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" />
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="%sveltekit.assets%/favicon-16x16.png" />
<link rel="manifest" href="%sveltekit.assets%/site.webmanifest" />
<link rel="mask-icon" href="%sveltekit.assets%/safari-pinned-tab.svg" color="#000000" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
<body
data-sveltekit-preload-data="hover"
class="min-h-screen bg-background font-sans antialiased"
>
<div style="display: contents" class="relative flex min-h-screen flex-col">
%sveltekit.body%
</div>
</body>
</html>
</html>

View file

@ -3,69 +3,49 @@
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 83.1%;
}
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 72.2% 50.6%;
--primary-foreground: 0 85.7% 97.3%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 72.2% 50.6%;
--radius: 0rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 72.2% 50.6%;
--primary-foreground: 0 85.7% 97.3%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 72.2% 50.6%;
}
}
@layer base {

View file

@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" {...$$restProps}>
<path d="M0 0h24v24H0z" />
<path d="M3 19h18l-9 -15z" />
</svg>

After

Width:  |  Height:  |  Size: 109 B

View file

@ -0,0 +1,14 @@
import type { Icon as LucideIcon } from 'lucide-svelte';
import { GithubLogo, VercelLogo, LinkedinLogo } from 'radix-icons-svelte';
import Logo from './logo.svelte';
import Svelte from './svelte.svelte';
export type Icon = LucideIcon;
export const Icons = {
logo: Logo,
gitHub: GithubLogo,
svelte: Svelte,
vercel: VercelLogo,
linkedIn: LinkedinLogo
};

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="9" x="3" y="3" rx="1" stroke="#FF6C22" />
<rect width="7" height="5" x="14" y="3" rx="1" stroke="#FF9209" />
<rect width="7" height="9" x="14" y="12" rx="1" stroke="#2B3499" />
<rect width="7" height="5" x="3" y="16" rx="1" stroke="#FFD099" />
</svg>

After

Width:  |  Height:  |  Size: 446 B

View file

@ -0,0 +1,15 @@
<svg
class="inline-svg"
stroke="currentColor"
fill="currentColor"
stroke-width="0"
role="img"
viewBox="0 0 24 24"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z"
/></svg
>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" {...$$restProps}>
<path stroke="none" d="M0 0h24v24H0z" />
<path d="M3 19h18l-9 -15z" />
</svg>

After

Width:  |  Height:  |  Size: 123 B

View file

@ -0,0 +1,8 @@
export { default as Metadata } from './metadata.svelte';
export { default as SiteFooter } from './site-footer.svelte';
export { default as SiteNavBar } from './site-navbar.svelte';
export { default as TailwindIndicator } from './tailwind-indicator.svelte';
export { default as ModeToggle } from './mode-toggle.svelte';;
export * from './icons';
export * from './nav';

View file

@ -0,0 +1,36 @@
<script lang="ts">
import { page } from '$app/stores';
import { siteConfig } from '$lib/config/site';
export let title: string = siteConfig.name;
$: title = $page.data?.name ? `${$page.data.name} — ${siteConfig.name}` : siteConfig.name;
$: description = $page.data?.subTitle ?? siteConfig.description;
$: ogImage = encodeURI(
`${siteConfig.ogImage}?title=${$page.data.title}&subTitle=${$page.data.subTitle}`
);
</script>
<svelte:head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="keywords" content={siteConfig.keywords} />
<meta name="author" content="Bart van der Braak" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={siteConfig.url} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:image:alt" content={siteConfig.name} />
<meta name="twitter:creator" content="Bart van der Braak" />
<meta property="og:title" content={title} />
<meta property="og:type" content="article" />
<meta property="og:url" content={siteConfig.url + $page.url.pathname} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:alt" content={siteConfig.name} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:description" content={description} />
<meta property="og:site_name" content={siteConfig.name} />
<meta property="og:locale" content="EN_US" />
</svelte:head>

View file

@ -0,0 +1,25 @@
<script lang="ts">
import { Moon, Sun } from 'lucide-svelte';
import { Button } from '../ui/button';
import * as DropdownMenu from '../ui/dropdown-menu';
import { resetMode, setMode } from 'mode-watcher';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button builders={[builder]} variant="ghost" class="h-9 w-9">
<Sun
class="absolute h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
<Moon
class="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<span class="sr-only">Toggle theme</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item on:click={() => setMode('light')}>Light</DropdownMenu.Item>
<DropdownMenu.Item on:click={() => setMode('dark')}>Dark</DropdownMenu.Item>
<DropdownMenu.Item on:click={() => resetMode()}>System</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>

View file

@ -0,0 +1,2 @@
export { default as MainNav } from './main-nav.svelte';
export { default as MobileNav } from './mobile-nav.svelte';

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { page } from '$app/stores';
import { cn } from '$lib/utils';
import { navConfig } from '$lib/config/nav';
</script>
<div class="mr-4 hidden md:flex">
<nav class="flex items-center space-x-6 text-sm font-medium">
{#each navConfig.mainNav as navItem, index (navItem + index.toString())}
{#if navItem.href}
<a
href={navItem.href}
class={cn(
'transition-colors hover:text-foreground/80',
$page.url.pathname === navItem.href ? 'text-foreground' : 'text-foreground/60'
)}
>
{navItem.title}
</a>
{/if}
{/each}
</nav>
</div>

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { page } from '$app/stores';
import { cn } from '$lib/utils';
export let href: string;
export let open: boolean;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<a
{href}
on:click={() => (open = false)}
class={cn(
$page.url.pathname === href ? 'text-foreground' : 'text-foreground/60',
'hover:text-foreground',
className
)}
{...$$restProps}
>
<slot />
</a>

View file

@ -0,0 +1,65 @@
<script lang="ts">
import * as Sheet from '$lib/components/ui/sheet/';
import { HamburgerMenu } from 'radix-icons-svelte';
import { Button } from '$lib/components/ui/button';
import { navConfig } from '$lib/config/nav';
import { siteConfig } from '$lib/config/site';
import { Icons } from '../icons';
import MobileLink from './mobile-link.svelte';
let open = false;
</script>
<Sheet.Root bind:open>
<Sheet.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="ghost"
class="mr-2 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
>
<HamburgerMenu class="h-5 w-5" />
<span class="sr-only">Toggle Menu</span>
</Button>
</Sheet.Trigger>
<Sheet.Content side="right" class="pr-0">
<MobileLink href="/" class="flex items-center" bind:open>
<span class="sr-only">Logo icon (return home)</span>
<img src={Icons.logoIcon} class="mr-2 h-4 w-4" alt="icon of hellob.art" />
<span class="font-mono font-bold tracking-tighter">{siteConfig.name}</span>
</MobileLink>
<div class="my-4 h-[calc(100vh-8rem)] overflow-auto pl-1 pt-10">
<div class="flex flex-col space-y-3">
{#each navConfig.mainNav as navItem, index (navItem + index.toString())}
{#if navItem.href}
<MobileLink href={navItem.href} bind:open class="pt-2 text-5xl font-bold">
{navItem.title}
</MobileLink>
{/if}
{/each}
</div>
<div class="flex flex-col space-y-2">
{#each navConfig.sidebarNav as navItem, index (index)}
<div class="flex flex-col space-y-3 pt-6">
<h4 class="font-medium">{navItem.title}</h4>
{#if navItem?.items?.length}
{#each navItem.items as item}
{#if !item.disabled && item.href}
<MobileLink href={item.href} bind:open class="text-muted-foreground">
{item.title}
{#if item.label}
<span
class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000]"
>
{item.label}
</span>
{/if}
</MobileLink>
{/if}
{/each}
{/if}
</div>
{/each}
</div>
</div>
</Sheet.Content>
</Sheet.Root>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { Blocks, Cloud } from 'lucide-svelte';
import { siteConfig } from '$lib/config/site';
import { Code } from 'radix-icons-svelte';
</script>
<footer class="container py-6">
<div class="space-y-1">
<p class="text-center text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} &mdash; {siteConfig.name} by {siteConfig.author}. Licensed under GPL-3.0.
</p>
</div>
</footer>

View file

@ -0,0 +1,12 @@
<script lang="ts">
import * as PageHeader from './page-header';
export let title: string, subTitle: string;
</script>
<PageHeader.Root class="pb-8">
<PageHeader.Heading>{title}</PageHeader.Heading>
<PageHeader.Description>
{subTitle}
</PageHeader.Description>
<slot />
</PageHeader.Root>

View file

@ -0,0 +1,52 @@
<script lang="ts">
import { Icons, ModeToggle, MainNav, MobileNav } from '$lib/components/site';
import { siteConfig } from '$lib/config/site';
import { cn } from '$lib/utils';
import { buttonVariants } from '../ui/button';
</script>
<header
class="sticky top-0 z-50 w-full border-b bg-background/95 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-background/60"
>
<div class="container flex h-14 items-center">
<a href="/" class="mr-6 flex items-center space-x-2">
<span class="sr-only">Logo (return home)</span>
<Icons.logo />
</a>
<MainNav />
<div class="flex flex-1 items-center justify-between space-x-2 sm:space-x-4 md:justify-end">
<nav class="flex">
<a href={siteConfig.links.gitHubProfile} target="_blank" rel="noopener noreferrer">
<div
class={cn(
buttonVariants({
size: 'sm',
variant: 'ghost'
}),
'h-9 w-9 px-0'
)}
>
<Icons.gitHub class="h-4 w-4" />
<span class="sr-only">GitHub</span>
</div>
</a>
<a href={siteConfig.links.linkedIn} target="_blank" rel="noreferrer">
<div
class={cn(
buttonVariants({
size: 'sm',
variant: 'ghost'
}),
'h-9 w-9 px-0'
)}
>
<Icons.linkedIn class="h-4 w-4" />
<span class="sr-only">LinkedIn</span>
</div>
</a>
<ModeToggle />
</nav>
</div>
<MobileNav />
</div>
</header>

View file

@ -0,0 +1,10 @@
<div
class="fixed bottom-1 left-1 z-50 flex h-6 w-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white"
>
<div class="block sm:hidden">xs</div>
<div class="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">sm</div>
<div class="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
<div class="hidden lg:block xl:hidden 2xl:hidden">lg</div>
<div class="hidden xl:block 2xl:hidden">xl</div>
<div class="hidden 2xl:block">2xl</div>
</div>

View file

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { Check } from "radix-icons-svelte";
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-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none 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,26 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils";
type $$Props = DropdownMenuPrimitive.ContentProps;
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";
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-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
{...$$restProps}
>
<slot />
</DropdownMenuPrimitive.Item>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
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 { cn } from "$lib/utils";
import { DotFilled } from "radix-icons-svelte";
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
let className: $$Props["class"] = undefined;
export let value: DropdownMenuPrimitive.RadioItemProps["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-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none 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>
<DotFilled class="h-4 w-4 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";
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";
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,29 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils";
type $$Props = DropdownMenuPrimitive.SubContentProps;
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 { cn } from "$lib/utils";
import { ChevronRight } from "radix-icons-svelte";
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,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,41 @@
<script lang="ts">
import { Dialog as SheetPrimitive } from "bits-ui";
import { SheetOverlay, SheetPortal, sheetVariants, sheetTransitions, type Side } from ".";
import { Cross2 } from "radix-icons-svelte";
import { cn } from "$lib/utils";
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"
>
<Cross2 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";
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";
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";
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";
import { fade } from "svelte/transition";
type $$Props = SheetPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export { className as class };
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150
};
</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";
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";
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,24 @@
import type { NavItem, SidebarNavItem } from '$lib/types/nav';
interface NavConfig {
mainNav: NavItem[];
sidebarNav: SidebarNavItem[];
}
export const navConfig: NavConfig = {
mainNav: [
{
title: 'Home',
href: '/'
},
{
title: 'Login',
href: '/login'
},
{
title: 'Signup',
href: '/signup'
}
],
sidebarNav: []
};

View file

@ -0,0 +1,17 @@
const SITE_URL =
import.meta.env.VERCEL_ENV === 'preview' ? import.meta.env.VERCEL_URL : 'omnidash.io';
export const siteConfig = {
name: 'Omnidash',
author: 'Bart van der Braak',
url: SITE_URL,
description: 'Self-hostable dashboard using connectors to a multitude of ticketing systems.',
ogImage: `https://${SITE_URL}/og.png`,
links: {
gitHubProfile: 'https://github.com/bartvdbraak',
gitHubProject: 'https://github.com/bartvdbraak/omnidash',
},
keywords: `Ticket,Dashboard,Self-hosted,${SITE_URL},Bart van der Braak,Omnidash`,
};
export type SiteConfig = typeof siteConfig;

View file

@ -0,0 +1,18 @@
import type { Icons } from '$lib/components/site/icons';
export type NavItem = {
title: string;
href: string;
disabled?: boolean;
external?: boolean;
icon?: keyof typeof Icons;
label?: string;
};
export type SidebarNavItem = NavItem & {
items: SidebarNavItem[];
};
export type NavItemWithChildren = NavItem & {
items: NavItemWithChildren[];
};

View file

@ -1,5 +1,26 @@
<script>
<script lang="ts">
import { dev } from '$app/environment';
import { Metadata, SiteFooter, SiteNavBar, TailwindIndicator } from '$lib/components/site';
import { ModeWatcher } from 'mode-watcher';
import '../app.pcss';
import { fade } from 'svelte/transition';
</script>
<slot />
<ModeWatcher />
<Metadata />
<div class="relative flex min-h-screen flex-col" id="page">
<SiteNavBar />
<main class="container relative mb-4 max-w-[980px] flex-1">
<!-- {#key data.url} -->
<div in:fade={{ duration: 200, delay: 100 }} out:fade={{ duration: 100 }}>
<slot />
</div>
<!-- {/key} -->
</main>
<SiteFooter />
{#if dev}
<TailwindIndicator />
{/if}
</div>

View file

@ -16,11 +16,3 @@
</div>
{/if}
<style>
.links {
display: flex;
gap: 1rem;
align-items: center;
font-size: 1.5rem;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
apps/web/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="9" x="3" y="3" rx="1" stroke="#FF6C22" />
<rect width="7" height="5" x="14" y="3" rx="1" stroke="#FF9209" />
<rect width="7" height="9" x="14" y="12" rx="1" stroke="#2B3499" />
<rect width="7" height="5" x="3" y="16" rx="1" stroke="#FFD099" />
</svg>

After

Width:  |  Height:  |  Size: 446 B

View file

@ -0,0 +1,40 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1128 6415 c-2 -2 -28 -5 -58 -8 -164 -18 -339 -147 -421 -312 -68
-138 -66 -90 -64 -1310 l2 -1100 22 -65 c59 -170 173 -296 326 -362 56 -23 75
-29 145 -43 47 -10 1604 -6 1656 4 208 40 377 194 450 411 18 52 19 115 21
1155 3 1159 3 1163 -42 1268 -19 45 -72 133 -100 165 -25 29 -129 111 -170
133 -16 9 -61 27 -100 40 l-70 22 -797 2 c-439 1 -799 1 -800 0z m1482 -584
c13 -1 15 -120 15 -1018 l0 -1018 -729 0 -729 0 -1 1013 c-1 769 2 1015 11
1020 9 5 1347 8 1433 3z"/>
<path d="M4317 6414 c-1 -1 -20 -4 -43 -7 -88 -12 -225 -79 -297 -146 -46 -42
-113 -141 -140 -206 -45 -112 -48 -149 -44 -696 4 -545 2 -526 52 -644 64
-150 213 -274 386 -321 58 -16 134 -17 854 -18 435 0 810 3 835 7 236 33 424
210 487 458 3 13 7 256 8 539 2 547 0 572 -47 683 -65 154 -198 274 -363 328
l-70 23 -808 1 c-444 1 -808 1 -810 -1z m1509 -587 c9 -8 11 -845 3 -858 -6
-10 -1432 -15 -1447 -6 -5 4 -9 844 -3 867 2 8 1440 4 1447 -3z"/>
<path d="M4218 3769 c-207 -58 -367 -229 -413 -440 -21 -98 -21 -2184 0 -2282
45 -207 190 -368 395 -436 70 -24 138 -26 930 -25 l805 0 78 27 c98 34 172 81
243 154 65 68 129 189 147 282 11 51 13 296 12 1155 -1 600 -5 1100 -8 1111
-2 11 -10 40 -17 65 -34 128 -167 287 -290 347 -135 65 -105 63 -1002 62 -766
-1 -817 -2 -880 -20z m1613 -582 c5 -125 2 -2005 -3 -2012 -4 -7 -253 -9 -720
-8 -392 1 -718 2 -723 2 -7 1 -10 344 -9 1019 1 559 2 1018 3 1019 0 1 327 2
726 2 l725 0 1 -22z"/>
<path d="M1095 2619 c-62 -8 -175 -41 -175 -51 0 -4 -9 -8 -20 -8 -11 0 -20
-4 -20 -10 0 -5 -5 -10 -11 -10 -20 0 -111 -79 -158 -138 -25 -31 -58 -85 -73
-118 -53 -117 -55 -148 -53 -705 2 -458 4 -520 19 -569 64 -199 205 -342 399
-402 l72 -23 800 0 c768 1 852 3 921 24 191 57 356 235 400 432 12 51 15 1045
3 1104 -43 225 -207 401 -430 461 -58 16 -137 17 -844 18 -429 1 -802 -2 -830
-5z m1523 -584 c4 -4 7 -200 7 -436 0 -375 -2 -429 -15 -430 -110 -6 -1433 -2
-1437 5 -5 7 -8 799 -4 853 1 6 9 13 18 15 32 6 1425 -1 1431 -7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}