layout UI
This commit is contained in:
@@ -3,6 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<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%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<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