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