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.4 kB 154 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 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>