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.3 kB 155 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 LogotypeSmall from '../ui/LogotypeSmall.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 const navClass = $derived( 38 variant === 'marketing' 39 ? 'mx-auto w-full max-w-screen-xl space-x-4 px-4 py-2 sm:px-6' 40 : 'mx-auto w-full space-x-4 px-6 py-2' 41 ); 42</script> 43 44<nav class={navClass} aria-label="main"> 45 <div class="flex items-center justify-between gap-4 p-0"> 46 <div class="flex shrink-0 items-center gap-2"> 47 <a 48 href={resolve('/')} 49 class="flex shrink-0 items-center gap-2 no-underline hover:no-underline" 50 > 51 <span class="hidden sm:inline-flex"> 52 <LogotypeSmall /> 53 </span> 54 <span class="inline-flex sm:hidden"> 55 <img src="/logos/dolly.svg" alt="tangled" class="size-8" /> 56 </span> 57 </a> 58 </div> 59 60 <div class="flex items-center gap-4"> 61 {#if variant === 'marketing'} 62 <a href={appPath('/login')} class="text-fg transition-colors hover:text-fg-muted"> 63 Login 64 </a> 65 <span class="text-fg-subtle">or</span> 66 <Button variant="success" size="sm" href="/signup" class="!py-0"> 67 <span>Join now</span> 68 <ArrowRight class="size-4" aria-hidden="true" /> 69 </Button> 70 {:else} 71 {#if user || loading} 72 <div class="order-3 flex items-center md:order-1"> 73 <TopbarSearch /> 74 </div> 75 76 <div class="order-1 flex items-center md:order-3"> 77 <Dropdown id="new-menu" group="topbar" variant="button"> 78 {#snippet trigger()} 79 <Plus class="size-4" aria-hidden="true" /> 80 <span class="hidden md:inline">New</span> 81 {/snippet} 82 <DropdownItem href="/repo/new" icon={BookPlus}>New repository</DropdownItem> 83 <DropdownItem href="/strings/new" icon={LineSquiggle}>New string</DropdownItem> 84 </Dropdown> 85 </div> 86 87 <div class="order-2 flex items-center md:order-4"> 88 <a 89 href={appPath('/notifications')} 90 class="flex items-center text-fg-subtle transition-colors hover:text-fg" 91 aria-label="Notifications" 92 > 93 <Bell class="size-5" aria-hidden="true" /> 94 </a> 95 </div> 96 97 <div class="order-3 md:hidden"> 98 <a href={appPath('/search')} class="text-fg-subtle" aria-label="Search"> 99 <Search class="size-5" aria-hidden="true" /> 100 </a> 101 </div> 102 103 <div class="hidden h-6 w-px self-center bg-border md:order-2 md:block"></div> 104 {/if} 105 106 <div class="order-4 flex items-center md:order-5"> 107 {#if loading} 108 <div class="flex items-center gap-1" role="status" aria-label="Signing in"> 109 <span 110 class="flex size-8 items-center justify-center rounded-full border border-border bg-surface-muted text-fg-faint md:size-6" 111 > 112 <Spinner class="size-4" /> 113 </span> 114 <span class="hidden h-4 w-24 rounded bg-surface-muted md:block" aria-hidden="true" 115 ></span> 116 </div> 117 {:else if user} 118 <Dropdown id="profile-menu" group="topbar"> 119 {#snippet trigger()} 120 {#if user.avatar} 121 <img 122 src={user.avatar} 123 alt={user.handle} 124 class="size-8 rounded-full border border-border object-cover md:size-6" 125 /> 126 {:else} 127 <span 128 class="flex size-8 items-center justify-center rounded-full border border-border bg-surface-muted md:size-6" 129 > 130 <UserRound class="size-5 text-fg-faint md:size-4" aria-hidden="true" /> 131 </span> 132 {/if} 133 <span class="hidden max-w-48 truncate md:inline">{user.handle}</span> 134 {/snippet} 135 <DropdownItem href={`/${user.handle}`} icon={User}>Profile</DropdownItem> 136 <DropdownItem href={`/${user.handle}?tab=repos`} icon={BookMarked}> 137 Repositories 138 </DropdownItem> 139 <DropdownItem href={`/${user.handle}?tab=strings`} icon={LineSquiggle}> 140 Strings 141 </DropdownItem> 142 <DropdownItem href="/settings" icon={Cog}>Settings</DropdownItem> 143 <DropdownItem danger icon={LogOut} onclick={onSignOut}>Logout</DropdownItem> 144 </Dropdown> 145 {:else} 146 <div class="flex items-center gap-2" aria-hidden="true"> 147 <span class="size-8 rounded-full bg-surface-muted md:size-6"></span> 148 <span class="hidden h-4 w-24 rounded bg-surface-muted md:block"></span> 149 </div> 150 {/if} 151 </div> 152 {/if} 153 </div> 154 </div> 155</nav>