Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Svelte
at icy/tmrrpn
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224<script module lang="ts"> import { tv, type VariantProps } from "tailwind-variants";
export const topbar = tv({ slots: { nav: "h-11.5 w-full px-5", row: "flex h-full items-center justify-between gap-4", brand: "flex shrink-0 items-center gap-2", logo: "flex shrink-0 items-center gap-2 no-underline hover:no-underline", wordmark: "hidden sm:inline-flex", mark: "inline-flex sm:hidden", actions: "flex items-center", action: "flex items-center", divider: "mx-2 hidden h-6 w-px self-center rounded-md bg-background-inset md:block", link: "flex items-center text-foreground-default transition-colors hover:text-foreground-muted", identity: "flex items-center gap-1", ball: "flex size-8 items-center justify-center rounded-full border border-border-default bg-background-inset text-foreground-placeholder md:size-5", bar: "hidden h-4 w-24 rounded-sm bg-background-inset md:block", switchHeader: "border-b-0 px-2 py-1.5 typography-paragraph-small text-foreground-muted", addBall: "flex size-5.25 shrink-0 items-center justify-center rounded-full bg-background-muted text-foreground-default" }, variants: { variant: { marketing: { actions: "gap-3.5" }, app: { nav: "border-b border-border-default bg-background-default", actions: "gap-2" } } } });
export type TopbarVariants = VariantProps<typeof topbar>;</script>
<script lang="ts"> import { resolve } from "$app/paths"; import Button from "../ui/Button.svelte"; import Dropdown from "../ui/Dropdown.svelte"; import DropdownItem from "../ui/DropdownItem.svelte"; import ArrowRight from "$icon/arrow-right"; import BookMarked from "$icon/book-marked"; import BookPlus from "$icon/book-plus"; import Cog from "$icon/cog"; import LineSquiggle from "$icon/line-squiggle"; import LogOut from "$icon/log-out"; import Plus from "$icon/plus"; import Search from "$icon/search"; import UserIcon from "$icon/user"; import Spinner from "../ui/Spinner.svelte"; import Logo from "../ui/Logo.svelte"; import User from "../ui/User.svelte"; import TopbarSearch from "./TopbarSearch.svelte"; import NotificationBell from "../notifications/NotificationBell.svelte"; import type { AuthAccount, CurrentUser } from "$lib/auth.svelte"; import Separator from "../ui/Separator.svelte";
interface Props { variant: NonNullable<TopbarVariants["variant"]>; onSignOut?: () => void | Promise<void>; onSwitchAccount?: (did: AuthAccount["did"]) => void | Promise<void>; user: CurrentUser | null; accounts?: readonly AuthAccount[]; loginHref?: string; loading?: boolean; }
let { variant, onSignOut, onSwitchAccount, user, accounts = [], loginHref = "/login", loading = false }: Props = $props();
const appPath = (path: string) => resolve(path as "/");
const others = $derived(accounts.filter((account) => account.did !== user?.did));
const style = $derived(topbar({ variant }));</script>
<nav class={style.nav()} aria-label="main"> <div class={style.row()}> <div class={style.brand()}> <a href={resolve("/")} class={style.logo()}> <span class={style.wordmark()}> <Logo wordmark alpha class="h-6.5" /> </span> <span class={style.mark()}> <img src="/logos/dolly.svg" alt="tangled" class="size-8" /> </span> </a> </div>
<div class={style.actions()}> {#if variant === "marketing"} <Button variant="ghost" href={resolve("/login")}>Login</Button>
<span class="typography-paragraph-small text-foreground-muted">or</span>
<Button variant="primary" href={resolve("/signup")} insetShadow={true}> <span>Join now</span> <ArrowRight class="size-3" aria-hidden="true" /> </Button> {:else} {#if user || loading} <div class={style.action({ class: "order-3 hidden sm:block md:order-1" })}> <TopbarSearch /> </div>
<div class={style.action({ class: "order-1 md:order-3" })}> <Dropdown insetShadow={true} size="small" id="new-menu" group="topbar" variant="button" align="right" > {#snippet trigger()} <Plus class="size-3.5" aria-hidden="true" /> <span class="hidden md:inline">New</span> {/snippet} <DropdownItem href="/repo/new" icon={BookPlus} >New repository</DropdownItem > <DropdownItem href="/strings/new" icon={LineSquiggle} >New string</DropdownItem > </Dropdown> </div>
<div class={style.action({ class: "order-2 md:order-4" })}> <NotificationBell /> </div>
<div class="order-3 md:hidden"> <Button variant="ghost" href={appPath("/search")} icon={Search} size="small" aria-hidden="true" aria-label="Search" /> </div>
<div class={style.divider({ class: "md:order-2" })}></div> {/if}
<div class={style.action({ class: "order-4 md:order-5" })}> {#if loading} <div class={style.identity()} role="status" aria-label="Signing in"> <span class={style.ball()}> <Spinner class="size-4" /> </span> <span class={style.bar()} aria-hidden="true"></span> </div> {:else if user} <Dropdown id="profile-menu" group="topbar" align="right" buttonVariant="ghost" variant="button" size="small" > {#snippet trigger()} <User did={user.did} handle={user.handle} /> {/snippet} {#if others.length > 0} <div class={style.switchHeader()}>Switch account</div> {#each others as account (account.did)} <DropdownItem onclick={() => onSwitchAccount?.(account.did)}> <User did={account.did} handle={account.handle} /> </DropdownItem> {/each} {/if} <DropdownItem href={loginHref}> <span class={style.addBall()}> <Plus class="size-3" aria-hidden="true" /> </span> Add account </DropdownItem> <Separator /> <DropdownItem href={`/${user.handle}`} icon={UserIcon} >Profile</DropdownItem > <DropdownItem href={`/${user.handle}?tab=repos`} icon={BookMarked}> Repositories </DropdownItem> <DropdownItem href={`/${user.handle}?tab=strings`} icon={LineSquiggle}> Strings </DropdownItem> <DropdownItem href="/settings" icon={Cog}>Settings</DropdownItem> <DropdownItem danger icon={LogOut} onclick={onSignOut} >Logout</DropdownItem > </Dropdown> {:else} <div class={style.identity({ class: "gap-2" })}> <Button variant="ghost" size="small" href={appPath(loginHref)} >Login</Button > <Button variant="primary" size="small" href={resolve("/signup")} insetShadow={true} > Join now </Button> </div> {/if} </div> {/if} </div> </div></nav>