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 UserIcon from "$icon/user";
16 import Spinner from "../ui/Spinner.svelte";
17 import Logo from "../ui/Logo.svelte";
18 import User from "../ui/User.svelte";
19 import TopbarSearch from "./TopbarSearch.svelte";
20 import type { CurrentUser } from "$lib/auth.svelte";
21 import Separator from "../ui/Separator.svelte";
22
23 interface Props {
24 /** `bare` is the onboarding chrome: the wordmark alone, nothing to click away to */
25 variant: "marketing" | "app" | "bare";
26 onSignOut?: () => void | Promise<void>;
27 user: CurrentUser | null;
28 loading?: boolean;
29 }
30
31 let { variant, onSignOut, user, loading = false }: Props = $props();
32
33 const appPath = (path: string) => resolve(path as "/");
34</script>
35
36{#snippet divider(classes = "")}
37 <div
38 class={["hidden h-6 w-px self-center rounded-md bg-background-inset md:block", classes]}
39 ></div>
40{/snippet}
41
42<nav
43 class={`h-11 w-full px-5
44 ${variant === "app" ? "border-b border-border-default bg-background-default shadow-[0_1px_1px_0_rgba(0,0,0,0.05)]" : ""}
45 `}
46 aria-label="main"
47>
48 <div class="flex h-full items-center justify-between gap-4">
49 <div class="flex shrink-0 items-center gap-2">
50 <a
51 href={resolve("/")}
52 class="flex shrink-0 items-center gap-2 no-underline hover:no-underline"
53 >
54 <span class="hidden sm:inline-flex">
55 <Logo wordmark alpha class="h-6.5" />
56 </span>
57 <span class="inline-flex sm:hidden">
58 <img src="/logos/dolly.svg" alt="tangled" class="size-8" />
59 </span>
60 </a>
61 </div>
62
63 <div class={variant === "marketing" ? "flex items-center gap-3.5" : "flex items-center gap-4"}>
64 {#if variant === "bare"}
65 <!-- nothing: onboarding has no escape hatches in the bar itself -->
66 {:else if variant === "marketing"}
67 <TopbarSearch />
68
69 {@render divider()}
70
71 <Button variant="ghost" href={resolve("/login")}>Login</Button>
72
73 <span class="typography-paragraph-small 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
124 class="hidden h-4 w-24 rounded-sm bg-background-inset md:block"
125 aria-hidden="true"
126 ></span>
127 </div>
128 {:else if user}
129 <Dropdown id="profile-menu" group="topbar" align="right">
130 {#snippet trigger()}
131 <User did={user.did} handle={user.handle} />
132 {/snippet}
133 <DropdownItem href={`/${user.handle}`} icon={UserIcon}>Profile</DropdownItem>
134 <DropdownItem href={`/${user.handle}?tab=repos`} icon={BookMarked}>
135 Repositories
136 </DropdownItem>
137 <DropdownItem href={`/${user.handle}?tab=strings`} icon={LineSquiggle}>
138 Strings
139 </DropdownItem>
140 <DropdownItem href="/settings" icon={Cog}>Settings</DropdownItem>
141 <Separator/>
142 <DropdownItem danger icon={LogOut} onclick={onSignOut}>Logout</DropdownItem>
143 </Dropdown>
144 {:else}
145 <div class="flex items-center gap-2" aria-hidden="true">
146 <span class="size-8 rounded-full bg-background-inset md:size-5"></span>
147 <span class="hidden h-4 w-24 rounded-sm bg-background-inset md:block"></span>
148 </div>
149 {/if}
150 </div>
151 {/if}
152 </div>
153 </div>
154</nav>