layout UI
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=Geist+Mono:wght@400;500&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
29
src/lib/components/Header.svelte
Normal file
29
src/lib/components/Header.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
|
||||
const user = $derived(page.data?.user);
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="sticky top-0 z-30 flex h-14 items-center justify-between border-b border-border bg-surface-900/80 px-6 backdrop-blur-md"
|
||||
>
|
||||
<div></div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
{#if user}
|
||||
<div class="flex items-center gap-3">
|
||||
{#if user.image}
|
||||
<img src={user.image} alt={user.name} class="h-7 w-7 rounded-full" />
|
||||
{/if}
|
||||
<span class="font-500 text-sm text-text-primary">{user.name}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<a
|
||||
href="/api/auth/osu/login"
|
||||
class="font-600 rounded-md bg-accent px-3.5 py-1.5 text-sm text-surface-900 transition-colors hover:bg-accent-hover"
|
||||
>
|
||||
Sign in with osu!
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
57
src/lib/components/Sidebar.svelte
Normal file
57
src/lib/components/Sidebar.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
|
||||
const nav = [
|
||||
{ href: '/', label: 'Dashboard', icon: '⌂' },
|
||||
{ href: '/matches', label: 'Matches', icon: '⚔' },
|
||||
{ href: '/mappools', label: 'Mappools', icon: '♫' },
|
||||
{ href: '/teams', label: 'Teams', icon: '⚑' }
|
||||
];
|
||||
|
||||
function isActive(href: string) {
|
||||
if (href === '/') return page.url.pathname === '/';
|
||||
return page.url.pathname.startsWith(href);
|
||||
}
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="fixed top-0 left-0 z-40 flex h-full w-56 flex-col border-r border-border bg-surface-800"
|
||||
>
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex items-center gap-2.5 px-5 py-5">
|
||||
<div
|
||||
class="font-800 flex h-8 w-8 items-center justify-center rounded-md bg-accent text-sm text-surface-900"
|
||||
>
|
||||
V
|
||||
</div>
|
||||
<span class="font-700 text-base tracking-tight text-text-primary">VASH</span>
|
||||
</a>
|
||||
|
||||
<!-- Nav -->
|
||||
<nav class="mt-2 flex flex-1 flex-col gap-0.5 px-3">
|
||||
{#each nav as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class="font-500 flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors {isActive(
|
||||
item.href
|
||||
)
|
||||
? 'bg-accent-dim text-accent'
|
||||
: 'text-text-secondary hover:bg-surface-700 hover:text-text-primary'}"
|
||||
>
|
||||
<span class="text-base">{item.icon}</span>
|
||||
{item.label}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<!-- Bottom -->
|
||||
<div class="border-t border-border p-3">
|
||||
<a
|
||||
href="/settings"
|
||||
class="font-500 flex items-center gap-3 rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-700 hover:text-text-primary"
|
||||
>
|
||||
<span class="text-base">⚙</span>
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
7
src/routes/(app)/+layout.server.ts
Normal file
7
src/routes/(app)/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||
return {
|
||||
user: locals.user ?? null
|
||||
};
|
||||
};
|
||||
16
src/routes/(app)/+layout.svelte
Normal file
16
src/routes/(app)/+layout.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-dvh">
|
||||
<Sidebar />
|
||||
<div class="ml-56 flex flex-1 flex-col">
|
||||
<Header />
|
||||
<main class="flex-1 p-6">
|
||||
{@render children()}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
46
src/routes/(app)/+page.svelte
Normal file
46
src/routes/(app)/+page.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { PageProps } from './$types';
|
||||
import './layout.css';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<h1 class="font-700 text-2xl tracking-tight">
|
||||
{#if data.user}
|
||||
Welcome back, <span class="text-accent">{data.user.name}</span>
|
||||
{:else}
|
||||
Welcome to <span class="text-accent">VASH</span>
|
||||
{/if}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-text-secondary">Automated esports match management for osu!</p>
|
||||
|
||||
<div class="mt-8 grid grid-cols-3 gap-4">
|
||||
<a
|
||||
href="/matches"
|
||||
class="group rounded-lg border border-border bg-surface-800 p-5 transition-colors hover:border-accent/40 hover:bg-surface-700"
|
||||
>
|
||||
<div class="text-2xl">⚔</div>
|
||||
<h2 class="font-600 mt-3 text-sm">Matches</h2>
|
||||
<p class="mt-1 text-xs text-text-secondary">Create and manage competitive matches</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/mappools"
|
||||
class="group rounded-lg border border-border bg-surface-800 p-5 transition-colors hover:border-accent/40 hover:bg-surface-700"
|
||||
>
|
||||
<div class="text-2xl">♫</div>
|
||||
<h2 class="font-600 mt-3 text-sm">Mappools</h2>
|
||||
<p class="mt-1 text-xs text-text-secondary">Build and share beatmap pools</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/teams"
|
||||
class="group rounded-lg border border-border bg-surface-800 p-5 transition-colors hover:border-accent/40 hover:bg-surface-700"
|
||||
>
|
||||
<div class="text-2xl">⚑</div>
|
||||
<h2 class="font-600 mt-3 text-sm">Teams</h2>
|
||||
<p class="mt-1 text-xs text-text-secondary">Manage your teams and rosters</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
43
src/routes/(app)/layout.css
Normal file
43
src/routes/(app)/layout.css
Normal file
@@ -0,0 +1,43 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin '@tailwindcss/typography';
|
||||
|
||||
@theme {
|
||||
--color-surface-900: #0a0a0f;
|
||||
--color-surface-800: #12121a;
|
||||
--color-surface-700: #1a1a25;
|
||||
--color-surface-600: #24243a;
|
||||
--color-accent: #ff66ab;
|
||||
--color-accent-hover: #ff85be;
|
||||
--color-accent-dim: #ff66ab33;
|
||||
--color-text-primary: #eeeef0;
|
||||
--color-text-secondary: #8888a0;
|
||||
--color-border: #2a2a3d;
|
||||
--font-display: 'Geist', system-ui, sans-serif;
|
||||
--font-body: 'Geist', system-ui, sans-serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--color-surface-900);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-surface-900);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-surface-600);
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<script lang="ts">
|
||||
import './layout.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
||||
{@render children()}
|
||||
@@ -1,2 +0,0 @@
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
@@ -1,2 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin '@tailwindcss/typography';
|
||||
@@ -1,13 +0,0 @@
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import Page from './+page.svelte';
|
||||
|
||||
describe('/+page.svelte', () => {
|
||||
it('should render h1', async () => {
|
||||
render(Page);
|
||||
|
||||
const heading = page.getByRole('heading', { level: 1 });
|
||||
await expect.element(heading).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user