Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199<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", ball: "size-8 rounded-full bg-background-inset md:size-5", bar: "hidden h-4 w-24 rounded-sm bg-background-inset md:block" }, variants: { variant: { marketing: { actions: "gap-3.5" }, app: { nav: "border-b border-border-default bg-background-default", actions: "gap-2" } }, // The signing-in placeholder wraps a spinner. pending: { true: { identity: "gap-1", ball: "flex items-center justify-center border border-border-default text-foreground-placeholder" }, false: { identity: "gap-2" } } }, defaultVariants: { pending: false } });
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 Bell from "$icon/bell"; 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 type { CurrentUser } from "$lib/auth.svelte"; import Separator from "../ui/Separator.svelte";
interface Props { variant: NonNullable<TopbarVariants["variant"]>; onSignOut?: () => void | Promise<void>; user: CurrentUser | null; loading?: boolean; }
let { variant, onSignOut, user, loading = false }: Props = $props();
const appPath = (path: string) => resolve(path as "/");
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"} <TopbarSearch />
<div class={style.divider()}></div>
<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" })}> <Button variant="ghost" href={appPath("/notifications")} icon={Bell} size="small" aria-hidden="true" aria-label="Notifications" /> </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({ pending: true })} role="status" aria-label="Signing in"> <span class={style.ball({ pending: true })}> <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} <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> <Separator /> <DropdownItem danger icon={LogOut} onclick={onSignOut}>Logout</DropdownItem> </Dropdown> {:else} <div class={style.identity()} aria-hidden="true"> <span class={style.ball()}></span> <span class={style.bar()}></span> </div> {/if} </div> {/if} </div> </div></nav>