This repository has no description
1<script lang="ts">
2 import { resolve } from '$app/paths';
3 import Button from '../ui/Button.svelte';
4 import Dropdown from '../ui/Dropdown.svelte';
5 import DropdownItem from '../ui/DropdownItem.svelte';
6 import ArrowRight from '$icon/arrow-right';
7 import Bell from '$icon/bell';
8 import BookMarked from '$icon/book-marked';
9 import BookPlus from '$icon/book-plus';
10 import Cog from '$icon/cog';
11 import LineSquiggle from '$icon/line-squiggle';
12 import LogOut from '$icon/log-out';
13 import Plus from '$icon/plus';
14 import Search from '$icon/search';
15 import User from '$icon/user';
16 import UserRound from '$icon/user-round';
17 import Spinner from '../ui/Spinner.svelte';
18 import LogotypeSmall from '../ui/LogotypeSmall.svelte';
19 import TopbarSearch from './TopbarSearch.svelte';
20
21 interface User {
22 handle: string;
23 did: string;
24 avatar?: string;
25 }
26
27 interface Props {
28 variant: 'marketing' | 'app';
29 onSignOut?: () => void | Promise<void>;
30 user: User | null;
31 loading?: boolean;
32 }
33
34 let { variant, onSignOut, user, loading = false }: Props = $props();
35
36 const appPath = (path: string) => resolve(path as '/');
37 const navClass = $derived(
38 variant === 'marketing'
39 ? 'mx-auto w-full max-w-screen-xl space-x-4 px-4 py-2 sm:px-6'
40 : 'mx-auto w-full space-x-4 px-6 py-2'
41 );
42</script>
43
44<nav class={navClass} aria-label="main">
45 <div class="flex items-center justify-between gap-4 p-0">
46 <div class="flex shrink-0 items-center gap-2">
47 <a
48 href={resolve('/')}
49 class="flex shrink-0 items-center gap-2 no-underline hover:no-underline"
50 >
51 <span class="hidden sm:inline-flex">
52 <LogotypeSmall />
53 </span>
54 <span class="inline-flex sm:hidden">
55 <img src="/logos/dolly.svg" alt="tangled" class="size-8" />
56 </span>
57 </a>
58 </div>
59
60 <div class="flex items-center gap-4">
61 {#if variant === 'marketing'}
62 <a href={appPath('/login')} class="text-fg transition-colors hover:text-fg-muted">
63 Login
64 </a>
65 <span class="text-fg-subtle">or</span>
66 <Button variant="success" size="sm" href="/signup" class="!py-0">
67 <span>Join now</span>
68 <ArrowRight class="size-4" aria-hidden="true" />
69 </Button>
70 {:else}
71 {#if user || loading}
72 <div class="order-3 flex items-center md:order-1">
73 <TopbarSearch />
74 </div>
75
76 <div class="order-1 flex items-center md:order-3">
77 <Dropdown id="new-menu" group="topbar" variant="button">
78 {#snippet trigger()}
79 <Plus class="size-4" aria-hidden="true" />
80 <span class="hidden md:inline">New</span>
81 {/snippet}
82 <DropdownItem href="/repo/new" icon={BookPlus}>New repository</DropdownItem>
83 <DropdownItem href="/strings/new" icon={LineSquiggle}>New string</DropdownItem>
84 </Dropdown>
85 </div>
86
87 <div class="order-2 flex items-center md:order-4">
88 <a
89 href={appPath('/notifications')}
90 class="flex items-center text-fg-subtle transition-colors hover:text-fg"
91 aria-label="Notifications"
92 >
93 <Bell class="size-5" aria-hidden="true" />
94 </a>
95 </div>
96
97 <div class="order-3 md:hidden">
98 <a href={appPath('/search')} class="text-fg-subtle" aria-label="Search">
99 <Search class="size-5" aria-hidden="true" />
100 </a>
101 </div>
102
103 <div class="hidden h-6 w-px self-center bg-border md:order-2 md:block"></div>
104 {/if}
105
106 <div class="order-4 flex items-center md:order-5">
107 {#if loading}
108 <div class="flex items-center gap-1" role="status" aria-label="Signing in">
109 <span
110 class="flex size-8 items-center justify-center rounded-full border border-border bg-surface-muted text-fg-faint md:size-6"
111 >
112 <Spinner class="size-4" />
113 </span>
114 <span class="hidden h-4 w-24 rounded bg-surface-muted md:block" aria-hidden="true"
115 ></span>
116 </div>
117 {:else if user}
118 <Dropdown id="profile-menu" group="topbar">
119 {#snippet trigger()}
120 {#if user.avatar}
121 <img
122 src={user.avatar}
123 alt={user.handle}
124 class="size-8 rounded-full border border-border object-cover md:size-6"
125 />
126 {:else}
127 <span
128 class="flex size-8 items-center justify-center rounded-full border border-border bg-surface-muted md:size-6"
129 >
130 <UserRound class="size-5 text-fg-faint md:size-4" aria-hidden="true" />
131 </span>
132 {/if}
133 <span class="hidden max-w-48 truncate md:inline">{user.handle}</span>
134 {/snippet}
135 <DropdownItem href={`/${user.handle}`} icon={User}>Profile</DropdownItem>
136 <DropdownItem href={`/${user.handle}?tab=repos`} icon={BookMarked}>
137 Repositories
138 </DropdownItem>
139 <DropdownItem href={`/${user.handle}?tab=strings`} icon={LineSquiggle}>
140 Strings
141 </DropdownItem>
142 <DropdownItem href="/settings" icon={Cog}>Settings</DropdownItem>
143 <DropdownItem danger icon={LogOut} onclick={onSignOut}>Logout</DropdownItem>
144 </Dropdown>
145 {:else}
146 <div class="flex items-center gap-2" aria-hidden="true">
147 <span class="size-8 rounded-full bg-surface-muted md:size-6"></span>
148 <span class="hidden h-4 w-24 rounded bg-surface-muted md:block"></span>
149 </div>
150 {/if}
151 </div>
152 {/if}
153 </div>
154 </div>
155</nav>