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