Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
766 changes: 384 additions & 382 deletions apps/web/src/components/effects/rukia-frost.tsx

Large diffs are not rendered by default.

32 changes: 28 additions & 4 deletions apps/web/src/components/layout/app-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { lazy, Suspense } from "react";
import { lazy, Suspense, useEffect, useRef } from "react";
import { motion } from "framer-motion";

let isFirstAppMount = true;
import { AppSidebar } from "@/components/layout/app-sidebar";
import { GuildShell } from "@/components/layout/guild-shell";
import { GuildSidebarNavPlaceholder } from "@/components/layout/guild-sidebar-nav-placeholder";
Expand Down Expand Up @@ -30,6 +33,12 @@ const InstallAddonModal = lazy(() =>
);

export const AppLayout = () => {
const shouldAnimate = useRef(isFirstAppMount).current;

useEffect(() => {
isFirstAppMount = false;
}, []);

const location = useLocation();
const guildRouteMatch = useMatches({
select: (matches) =>
Expand All @@ -41,16 +50,31 @@ export const AppLayout = () => {
guildRouteMatch?.status === "success" &&
guildRouteMatch.loaderData !== undefined;

const hasEverResolvedGuild = useRef(false);
if (hasResolvedGuildRoute) {
hasEverResolvedGuild.current = true;
}
if (isUserRoute) {
hasEverResolvedGuild.current = false;
}

const showGuildNav = hasResolvedGuildRoute || hasEverResolvedGuild.current;

const sidebarNavigation = isUserRoute ? (
<UserSidebarNav />
) : hasResolvedGuildRoute ? (
) : showGuildNav ? (
<GuildsSidebarNav />
) : (
<GuildSidebarNavPlaceholder />
);

return (
<div className="flex h-dvh w-full flex-col overflow-hidden">
<motion.div
className="flex h-dvh w-full flex-col overflow-hidden"
initial={shouldAnimate ? { opacity: 0, scale: 0.96 } : false}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<Suspense fallback={null}>
<ThemeAnnouncement />
</Suspense>
Expand All @@ -77,6 +101,6 @@ export const AppLayout = () => {
<CreateGuildModal />
<InstallAddonModal />
</Suspense>
</div>
</motion.div>
);
};
65 changes: 38 additions & 27 deletions apps/web/src/components/layout/guild-breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
} from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
import { Button } from "@lootlog/ui/components/button";
import { ArrowLeft, ChevronRight } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { SidebarTrigger } from "@lootlog/ui/components/sidebar";
import { AnimatePresence, motion } from "framer-motion";
import { PageHeader } from "@/components/layout/page-header";
import { getNavigationInfo } from "./get-navigation-info";

Expand Down Expand Up @@ -94,43 +95,53 @@ export const GuildBreadcrumbs: FC = () => {
variant="ghost"
size="sm"
onClick={() => navigate({ to: navInfo.backPath as string })}
className="p-1 h-8 w-8"
className="p-1 h-8 w-8 rounded-full hover:bg-muted/50 transition-colors"
>
<ArrowLeft className="h-4 w-4" />
</Button>
)}
</div>

<div className="flex flex-1 min-w-0 items-center text-sm md:justify-center">
<div className="flex flex-1 min-w-0 items-center text-sm justify-center">
<div
ref={mobileBreadcrumbsRef}
className="flex-1 overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden md:overflow-hidden"
>
<div className="inline-flex min-w-max items-center gap-1 pr-1 md:flex md:min-w-0 md:w-full md:justify-center md:pr-0">
{navInfo.breadcrumbs.map((crumb, index) => (
<div
key={index}
className="flex shrink-0 items-center gap-1 md:min-w-0 md:last:shrink"
>
{crumb.path ? (
<Button
variant="ghost"
size="sm"
onClick={() => navigate({ to: crumb.path as string })}
className="h-auto p-1 text-sm font-semibold whitespace-nowrap hover:bg-accent/50"
<div className="inline-flex min-w-max items-center justify-center gap-1.5 pr-1 md:flex md:min-w-0 md:w-full md:pr-0">
<AnimatePresence mode="popLayout">
{navInfo.breadcrumbs.map((crumb, index) => {
const isLast = index === navInfo.breadcrumbs.length - 1;
return (
<motion.div
key={`${crumb.label}-${crumb.path ?? "current"}`}
className="flex shrink-0 items-center gap-1.5 md:min-w-0 md:last:shrink"
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 8 }}
transition={{ duration: 0.15 }}
>
{crumb.label}
</Button>
) : (
<span className="px-1 font-semibold whitespace-nowrap md:max-w-full md:truncate">
{crumb.label}
</span>
)}
{index < navInfo.breadcrumbs.length - 1 && (
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground" />
)}
</div>
))}
{crumb.path ? (
<button
type="button"
onClick={() => navigate({ to: crumb.path as string })}
className="text-xs text-muted-foreground/70 hover:text-foreground transition-colors duration-200 whitespace-nowrap cursor-pointer"
>
{crumb.label}
</button>
) : (
<span className="text-sm font-bold text-foreground whitespace-nowrap md:max-w-full md:truncate">
{crumb.label}
</span>
)}
{!isLast && (
<span className="text-xs text-muted-foreground/30 select-none">
/
</span>
)}
</motion.div>
);
})}
</AnimatePresence>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/layout/guild-nav-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const GuildNavCreate: FC = () => {
<Tooltip>
<TooltipTrigger asChild>
<Button
className="size-10 0"
className="size-11 0"
variant="secondary"
onClick={() => dispatch({ type: "OPEN" })}
>
Expand Down
24 changes: 15 additions & 9 deletions apps/web/src/components/layout/guild-nav-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import type { FC } from "react";
import { Link } from "@tanstack/react-router";
import { FrozenCircle } from "@/components/effects/rukia-frost";
import { motion } from "framer-motion";

export type GuildNavItemProps = {
guild: Guild;
Expand All @@ -38,11 +39,11 @@ const GuildNavItemComponent: FC<GuildNavItemProps> = ({
}
};

const avatarContent = (
const avatarElement = (
<Avatar
className={cn(
"size-12 border-solid border-4 transition-all border-transparent box-border rounded-xl hover:rounded-lg",
{ "border-primary rounded-lg": isActive },
"size-11 transition-all duration-200 rounded-lg hover:rounded-lg hover:scale-105",
isActive && !isRukiaTheme && "border-[3px] border-primary",
)}
>
<AvatarImage
Expand All @@ -59,20 +60,25 @@ const GuildNavItemComponent: FC<GuildNavItemProps> = ({
return (
<Tooltip>
<TooltipTrigger asChild>
<div className="w-full flex items-center justify-center mb-1">
<div className="relative w-full flex items-center justify-center mb-2">
{isActive && !isRukiaTheme && (
<motion.div
layoutId="guild-active-pill"
className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 rounded-full bg-primary shadow-[0_0_6px_var(--primary)/0.4]"
transition={{ type: "spring", stiffness: 350, damping: 30 }}
/>
)}
<Link
to={`/${guild.vanityUrl ?? guild.id}` as string}
draggable={false}
className="group block"
className="group/guild-item block"
onClick={handleClick}
style={{ pointerEvents: isDragging ? "none" : "auto" }}
>
{isRukiaTheme ? (
<FrozenCircle isActive={isActive} size={48}>
{avatarContent}
</FrozenCircle>
<FrozenCircle isActive={isActive}>{avatarElement}</FrozenCircle>
) : (
avatarContent
avatarElement
)}
</Link>
</div>
Expand Down
37 changes: 21 additions & 16 deletions apps/web/src/components/layout/guild-pinned-events-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useListEvents,
} from "@/lib/api/generated/main/events/events";
import { useEventsSettingsControllerGetSettings } from "@/lib/api/generated/main/event-settings/event-settings";
import { AnimatePresence, motion } from "framer-motion";

export const GuildPinnedEventsSection = ({
guildId,
Expand Down Expand Up @@ -58,23 +59,27 @@ export const GuildPinnedEventsSection = ({
)
.filter((event) => event !== undefined);

if (isEventsPending || isSettingsPending) {
return (
<div className="px-2 mb-3 pb-3 border-b border-border">
<div className="h-24 rounded-lg border border-yellow-500/20 bg-yellow-500/5" />
</div>
);
}

if (pinnedActiveEvents.length === 0) {
return null;
}
const isLoading = isEventsPending || isSettingsPending;
const hasPinnedEvents = pinnedActiveEvents.length > 0;

return (
<PinnedEventsBanner
events={pinnedActiveEvents}
guildId={guildId}
onNavigate={onNavigate}
/>
<AnimatePresence initial={false}>
{!isLoading && hasPinnedEvents && (
<motion.div
key="pinned-events"
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
className="overflow-hidden"
>
<PinnedEventsBanner
events={pinnedActiveEvents}
guildId={guildId}
onNavigate={onNavigate}
/>
</motion.div>
)}
</AnimatePresence>
);
};
4 changes: 2 additions & 2 deletions apps/web/src/components/layout/guild-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ export const GuildShell: FC<GuildShellProps> = ({ children, variant }) => {
const { t } = useTranslation();

return (
<div className="flex h-full min-h-0 w-full flex-row">
<div className="flex h-full min-h-0 w-full flex-row bg-background/60">
<div className="flex h-full min-h-0 w-full flex-col">
{variant === "ready" ? (
<GuildBreadcrumbs />
) : (
<PageHeader>
<div className="flex items-center gap-2">
<SidebarTrigger />
<span className="text-sm font-semibold">
<span className="text-sm font-bold text-primary">
{t("common.routeErrors.guildShellTitle")}
</span>
</div>
Expand Down
Loading
Loading