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</script>
38
39<nav
40 class="h-11 w-full border-b border-border-default bg-background-default px-5 shadow-[0_1px_1px_0_rgba(0,0,0,0.05)]"
41 aria-label="main"
42>
43 <div class="flex h-full items-center justify-between gap-4">
44 <div class="flex shrink-0 items-center gap-2">
45 <a
46 href={resolve("/")}
47 class="flex shrink-0 items-center gap-2 no-underline hover:no-underline"
48 >
49 <span class="hidden sm:inline-flex">
50 <LogotypeSmall />
51 </span>
52 <span class="inline-flex sm:hidden">
53 <img src="/logos/dolly.svg" alt="tangled" class="size-8" />
54 </span>
55 </a>
56 </div>
57
58 <div class={variant === "marketing" ? "flex items-center gap-3.5" : "flex items-center gap-4"}>
59 {#if variant === "marketing"}
60 <TopbarSearch />
61 <div class="hidden h-9 w-px rounded-md bg-background-inset md:block"></div>
62 <a
63 href={appPath("/login")}
64 class="px-3 text-sm text-foreground-default transition-colors hover:text-foreground-muted"
65 >
66 Login
67 </a>
68 <span class="text-sm text-foreground-muted">or</span>
69 <Button variant="primary" size="sm" href="/signup" class="px-3">
70 <span>Join now</span>
71 <ArrowRight class="size-3.5" aria-hidden="true" />
72 </Button>
73 {:else}
74 {#if user || loading}
75 <div class="order-3 flex items-center md:order-1">
76 <TopbarSearch />
77 </div>
78
79 <div class="order-1 flex items-center md:order-3">
80 <Dropdown id="new-menu" group="topbar" variant="button">
81 {#snippet trigger()}
82 <Plus class="size-3.5" aria-hidden="true" />
83 <span class="hidden md:inline">New</span>
84 {/snippet}
85 <DropdownItem href="/repo/new" icon={BookPlus}>New repository</DropdownItem>
86 <DropdownItem href="/strings/new" icon={LineSquiggle}>New string</DropdownItem>
87 </Dropdown>
88 </div>
89
90 <div class="order-2 flex items-center md:order-4">
91 <a
92 href={appPath("/notifications")}
93 class="flex items-center text-foreground-default transition-colors hover:text-foreground-muted"
94 aria-label="Notifications"
95 >
96 <Bell class="size-4" aria-hidden="true" />
97 </a>
98 </div>
99
100 <div class="order-3 md:hidden">
101 <a href={appPath("/search")} class="text-foreground-subtle" aria-label="Search">
102 <Search class="size-5" aria-hidden="true" />
103 </a>
104 </div>
105
106 <div
107 class="hidden h-9 w-px self-center rounded-md bg-background-inset md:order-2 md:block"
108 ></div>
109 {/if}
110
111 <div class="order-4 flex items-center md:order-5">
112 {#if loading}
113 <div class="flex items-center gap-1" role="status" aria-label="Signing in">
114 <span
115 class="flex size-8 items-center justify-center rounded-full border border-border-default bg-background-inset text-foreground-placeholder md:size-[21px]"
116 >
117 <Spinner class="size-4" />
118 </span>
119 <span class="hidden h-4 w-24 rounded bg-background-inset md:block" aria-hidden="true"
120 ></span>
121 </div>
122 {:else if user}
123 <Dropdown id="profile-menu" group="topbar">
124 {#snippet trigger()}
125 {#if user.avatar}
126 <img
127 src={user.avatar}
128 alt={user.handle}
129 class="size-8 rounded-full border border-border-default object-cover md:size-[21px]"
130 />
131 {:else}
132 <span
133 class="flex size-8 items-center justify-center rounded-full border border-border-default bg-background-inset md:size-[21px]"
134 >
135 <UserRound
136 class="size-5 text-foreground-placeholder md:size-4"
137 aria-hidden="true"
138 />
139 </span>
140 {/if}
141 <span class="hidden max-w-48 truncate md:inline">{user.handle}</span>
142 {/snippet}
143 <DropdownItem href={`/${user.handle}`} icon={User}>Profile</DropdownItem>
144 <DropdownItem href={`/${user.handle}?tab=repos`} icon={BookMarked}>
145 Repositories
146 </DropdownItem>
147 <DropdownItem href={`/${user.handle}?tab=strings`} icon={LineSquiggle}>
148 Strings
149 </DropdownItem>
150 <DropdownItem href="/settings" icon={Cog}>Settings</DropdownItem>
151 <DropdownItem danger icon={LogOut} onclick={onSignOut}>Logout</DropdownItem>
152 </Dropdown>
153 {:else}
154 <div class="flex items-center gap-2" aria-hidden="true">
155 <span class="size-8 rounded-full bg-background-inset md:size-[21px]"></span>
156 <span class="hidden h-4 w-24 rounded bg-background-inset md:block"></span>
157 </div>
158 {/if}
159 </div>
160 {/if}
161 </div>
162 </div>
163</nav>