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
6.1 kB 198 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 4 export const topbar = tv({ 5 slots: { 6 nav: "h-11.5 w-full px-5", 7 row: "flex h-full items-center justify-between gap-4", 8 brand: "flex shrink-0 items-center gap-2", 9 logo: "flex shrink-0 items-center gap-2 no-underline hover:no-underline", 10 wordmark: "hidden sm:inline-flex", 11 mark: "inline-flex sm:hidden", 12 actions: "flex items-center", 13 action: "flex items-center", 14 divider: "mx-2 hidden h-6 w-px self-center rounded-md bg-background-inset md:block", 15 link: "flex items-center text-foreground-default transition-colors hover:text-foreground-muted", 16 identity: "flex items-center", 17 ball: "size-8 rounded-full bg-background-inset md:size-5", 18 bar: "hidden h-4 w-24 rounded-sm bg-background-inset md:block" 19 }, 20 variants: { 21 variant: { 22 marketing: { actions: "gap-3.5" }, 23 app: { 24 nav: "border-b border-border-default bg-background-default", 25 actions: "gap-2" 26 } 27 }, 28 // The signing-in placeholder wraps a spinner, so its ball gets a border and tighter gap. 29 pending: { 30 true: { 31 identity: "gap-1", 32 ball: "flex items-center justify-center border border-border-default text-foreground-placeholder" 33 }, 34 false: { identity: "gap-2" } 35 } 36 }, 37 defaultVariants: { 38 pending: false 39 } 40 }); 41 42 export type TopbarVariants = VariantProps<typeof topbar>; 43</script> 44 45<script lang="ts"> 46 import { resolve } from "$app/paths"; 47 import Button from "../ui/Button.svelte"; 48 import Dropdown from "../ui/Dropdown.svelte"; 49 import DropdownItem from "../ui/DropdownItem.svelte"; 50 import ArrowRight from "$icon/arrow-right"; 51 import Bell from "$icon/bell"; 52 import BookMarked from "$icon/book-marked"; 53 import BookPlus from "$icon/book-plus"; 54 import Cog from "$icon/cog"; 55 import LineSquiggle from "$icon/line-squiggle"; 56 import LogOut from "$icon/log-out"; 57 import Plus from "$icon/plus"; 58 import Search from "$icon/search"; 59 import UserIcon from "$icon/user"; 60 import Spinner from "../ui/Spinner.svelte"; 61 import Logo from "../ui/Logo.svelte"; 62 import User from "../ui/User.svelte"; 63 import TopbarSearch from "./TopbarSearch.svelte"; 64 import type { CurrentUser } from "$lib/auth.svelte"; 65 import Separator from "../ui/Separator.svelte"; 66 67 interface Props { 68 variant: NonNullable<TopbarVariants["variant"]>; 69 onSignOut?: () => void | Promise<void>; 70 user: CurrentUser | null; 71 loading?: boolean; 72 } 73 74 let { variant, onSignOut, user, loading = false }: Props = $props(); 75 76 const appPath = (path: string) => resolve(path as "/"); 77 78 const style = $derived(topbar({ variant })); 79</script> 80 81<nav class={style.nav()} aria-label="main"> 82 <div class={style.row()}> 83 <div class={style.brand()}> 84 <a href={resolve("/")} class={style.logo()}> 85 <span class={style.wordmark()}> 86 <Logo wordmark alpha class="h-6.5" /> 87 </span> 88 <span class={style.mark()}> 89 <img src="/logos/dolly.svg" alt="tangled" class="size-8" /> 90 </span> 91 </a> 92 </div> 93 94 <div class={style.actions()}> 95 {#if variant === "marketing"} 96 <TopbarSearch /> 97 98 <div class={style.divider()}></div> 99 100 <Button variant="ghost" href={resolve("/login")}>Login</Button> 101 102 <span class="typography-paragraph-small text-foreground-muted">or</span> 103 104 <Button variant="primary" href={resolve("/signup")} insetShadow={true}> 105 <span>Join now</span> 106 <ArrowRight class="size-3" aria-hidden="true" /> 107 </Button> 108 {:else} 109 {#if user || loading} 110 <div class={style.action({ class: "order-3 hidden sm:block md:order-1" })}> 111 <TopbarSearch /> 112 </div> 113 114 <div class={style.action({ class: "order-1 md:order-3" })}> 115 <Dropdown 116 insetShadow={true} 117 size="sm" 118 id="new-menu" 119 group="topbar" 120 variant="button" 121 align="right" 122 > 123 {#snippet trigger()} 124 <Plus class="size-3.5" aria-hidden="true" /> 125 <span class="hidden md:inline">New</span> 126 {/snippet} 127 <DropdownItem href="/repo/new" icon={BookPlus}>New repository</DropdownItem> 128 <DropdownItem href="/strings/new" icon={LineSquiggle}>New string</DropdownItem> 129 </Dropdown> 130 </div> 131 132 <div class={style.action({ class: "order-2 md:order-4" })}> 133 <Button 134 variant="ghost" 135 href={appPath("/notifications")} 136 icon={Bell} 137 size="sm" 138 aria-hidden="true" 139 aria-label="Notifications" 140 /> 141 </div> 142 143 <div class="order-3 md:hidden"> 144 <Button 145 variant="ghost" 146 href={appPath("/search")} 147 icon={Search} 148 size="sm" 149 aria-hidden="true" 150 aria-label="Search" 151 /> 152 </div> 153 154 <div class={style.divider({ class: "md:order-2" })}></div> 155 {/if} 156 157 <div class={style.action({ class: "order-4 md:order-5" })}> 158 {#if loading} 159 <div class={style.identity({ pending: true })} role="status" aria-label="Signing in"> 160 <span class={style.ball({ pending: true })}> 161 <Spinner class="size-4" /> 162 </span> 163 <span class={style.bar()} aria-hidden="true"></span> 164 </div> 165 {:else if user} 166 <Dropdown 167 id="profile-menu" 168 group="topbar" 169 align="right" 170 variant="button" 171 buttonVariant="ghost" 172 size="sm" 173 > 174 {#snippet trigger()} 175 <User did={user.did} handle={user.handle} /> 176 {/snippet} 177 <DropdownItem href={`/${user.handle}`} icon={UserIcon}>Profile</DropdownItem> 178 <DropdownItem href={`/${user.handle}?tab=repos`} icon={BookMarked}> 179 Repositories 180 </DropdownItem> 181 <DropdownItem href={`/${user.handle}?tab=strings`} icon={LineSquiggle}> 182 Strings 183 </DropdownItem> 184 <DropdownItem href="/settings" icon={Cog}>Settings</DropdownItem> 185 <Separator /> 186 <DropdownItem danger icon={LogOut} onclick={onSignOut}>Logout</DropdownItem> 187 </Dropdown> 188 {:else} 189 <div class={style.identity()} aria-hidden="true"> 190 <span class={style.ball()}></span> 191 <span class={style.bar()}></span> 192 </div> 193 {/if} 194 </div> 195 {/if} 196 </div> 197 </div> 198</nav>