This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / shell / Topbar.svelte
5.6 kB 168 lines
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>