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
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"react": "^19.1.0",
"react-aria-components": "^1.8.0",
"react-dom": "^19.1.0",
"react-stately": "^3.39.0",
"tailwind-merge": "^3.3.0",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.5",
Expand All @@ -55,6 +56,7 @@
"@storybook/react": "^8.6.12",
"@storybook/react-vite": "^8.6.12",
"@storybook/test": "^8.6.12",
"@storybook/theming": "^8.6.14",
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@tanstack/router-plugin": "^1.120.2",
Expand Down
6 changes: 6 additions & 0 deletions apps/web/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 110 additions & 0 deletions apps/web/src/components/AppShell/NavLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { PropsWithChildren, ReactNode } from "react";
import TablerChevronRight from "~icons/tabler/chevron-right";
import { tv } from "tailwind-variants";
import { useToggleState } from "react-stately";
import { cn } from "@/utils/cn";
import { Button as RAC_Button } from "react-aria-components";
import { Link } from "@tanstack/react-router";

const navLink = tv({
base: "px-3 py-2.5 rounded-sm text-sm flex flex-row items-center justify-between w-full cursor-pointer transition-none select-none text-navlink-text",
variants: {
active: {
true: "bg-navlink-bg-active font-medium",
false: "bg-navlink-bg-inactive font-normal hover:scale-101",
},
},
});

interface NavLinkProps {
href?: string;
label: string;
description?: string;
leftSection?: ReactNode;
rightSection?: ReactNode;
active?: boolean;
initialExpanded?: boolean;
}

const NavLink = ({
href,
label,
description,
leftSection,
rightSection,
active = false,
initialExpanded = false,
children,
}: PropsWithChildren<NavLinkProps>) => {
const isExpandable = !!children;

const toggleState = useToggleState({ defaultSelected: initialExpanded });
const toggle = toggleState.toggle;

return (
<div>
{isExpandable ? (
<RAC_Button className={navLink({ active })} onPress={toggle}>
<div className="flex flex-row gap-2 items-center">
{leftSection && (
<span className="flex items-center justify-center">
{leftSection}
</span>
)}
<div className="flex flex-col gap-0.5">
<span>{label}</span>
{description && (
<span className="text-xs text-navlink-secondary-text">
{description}
</span>
)}
</div>
</div>

<span
className={cn(
"flex items-center justify-center transition-transform duration-150",
toggleState.isSelected ? "rotate-90" : "rotate-0",
)}
>
<TablerChevronRight className="w-4 aspect-square" />
</span>
</RAC_Button>
) : (
<Link className={navLink({ active })} to={href}>
<div className="flex flex-row gap-2 items-center">
{leftSection && (
<span className="flex items-center justify-center">
{leftSection}
</span>
)}
<div className="flex flex-col gap-0.5">
<span>{label}</span>
{description && (
<span className="text-xs text-navlink-secondary-text">
{description}
</span>
)}
</div>
</div>
{rightSection && (
<span className="flex items-center justify-center">
{rightSection}
</span>
)}
</Link>
)}

{/* Expanded children links */}
<div>
{isExpandable && toggleState.isSelected && (
<div className="pl-6 mt-1">{children}</div>
)}
</div>
</div>
);
};

NavLink.displayName = "NavLink";

export { NavLink };
84 changes: 84 additions & 0 deletions apps/web/src/components/AppShell/stories/NavLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* This is bugged out brother */
import type { Meta, StoryObj } from "@storybook/react";
import TablerLayoutCollage from "~icons/tabler/layout-collage";
import { NavLink } from "../NavLink";
import {
createRootRouteWithContext,
createRouter,
RouterProvider,
} from "@tanstack/react-router";
import type { ReactNode } from "react";

// Define a dummy root component that wraps Story
function withTanstackRouter(StoryComponent: () => ReactNode) {
const RootRoute = createRootRouteWithContext()({
component: StoryComponent,
});

const router = createRouter({
routeTree: RootRoute,
});

return <RouterProvider router={router} />;
}

const meta = {
component: NavLink,
title: "AppShell/NavLink",
tags: ["autodocs"],
decorators: [(Story) => withTanstackRouter(Story)],
argTypes: {
href: {
control: { type: "text" },
description: "The URL the link points to",
defaultValue: "https://core.apidocumentation.com",
},
label: {
control: { type: "text" },
description: "The text displayed in the link",
defaultValue: "Dashboard",
},
active: {
control: { type: "boolean" },
description: "Indicates if the link is active",
defaultValue: false,
},
},
args: {
href: "https://core.apidocumentation.com",
label: "Dashboard",
active: true,
leftSection: (
<TablerLayoutCollage strokeWidth={0} className="w-4 aspect-square" />
),
},
} satisfies Meta<typeof NavLink>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
label: "Dashboard",
href: "https://core.apidocumentation.com",
},
};

export const Expandable: Story = {
args: {
label: "Mother",
children: (
<>
<NavLink label="Child 1" />
<NavLink label="Child 2" />
</>
),
},
};

export const WithDescription: Story = {
args: {
label: "Resume Review",
description: "67/67 (100%) resumes reviewed.",
},
};
4 changes: 2 additions & 2 deletions apps/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ body,
}

/* https://stackoverflow.com/questions/68400074/how-to-apply-transition-effects-when-switching-from-light-mode-to-dark-in-tailwi */
body.enable-theme-transition * {
@apply transition-colors duration-500;
body.enable-theme-transition {
@apply transition-colors duration-300;
}
9 changes: 9 additions & 0 deletions apps/web/src/routes/_protected/community.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_protected/community")({
component: RouteComponent,
});

function RouteComponent() {
return <div>Hello this is the community tab!</div>;
}
67 changes: 65 additions & 2 deletions apps/web/src/routes/_protected/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
import { NavLink } from "@/components/AppShell/NavLink";
import {
createFileRoute,
Outlet,
redirect,
useLocation,
} from "@tanstack/react-router";
import TablerLayoutCollage from "~icons/tabler/layout-collage";
import TablerBooks from "~icons/tabler/books";
import TablerSocial from "~icons/tabler/social";

// This layout component performs authentication checks before the user can access protected pages
export const Route = createFileRoute("/_protected")({
Expand Down Expand Up @@ -34,5 +43,59 @@ export const Route = createFileRoute("/_protected")({
});

function RouteComponent() {
return <Outlet />;
const pathname = useLocation({ select: (loc) => loc.pathname });

return (
<div className="flex h-screen w-screen flex-col">
<header className="h-14 w-full bg-gray-800 text-white flex items-center px-4 shadow">
<h1 className="text-lg font-semibold">SwampHacks</h1>
</header>

<div className="flex flex-1">
<aside className="w-64 px-2 py-4 border-r-1 border-neutral-300 dark:border-neutral-800">
<nav className="flex flex-col gap-2">
<NavLink
label="Dashboard"
description="Hello everyone"
href="/dashboard"
leftSection={
<TablerLayoutCollage className="w-4 aspect-square" />
}
active={pathname.startsWith("/dashboard")}
/>

<NavLink
label="Resources"
href="/_protected/resources"
initialExpanded={pathname.startsWith("/resources")}
leftSection={<TablerBooks className="w-4 aspect-square" />}
>
<NavLink
label="Programming"
href="/resources/programming"
active={pathname.startsWith("/resources/programming")}
/>
<NavLink
label="Sponsors"
href="/resources/sponsors"
active={pathname.startsWith("/resources/sponsors")}
/>
</NavLink>

<NavLink
label="Community"
href="/community"
leftSection={<TablerSocial className="w-4 aspect-square" />}
active={pathname.startsWith("/community")}
/>
</nav>
</aside>

{/* Main content */}
<main className="flex-1 p-6 overflow-y-auto">
<Outlet />
</main>
</div>
</div>
);
}
9 changes: 9 additions & 0 deletions apps/web/src/routes/_protected/resources/programming.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_protected/resources/programming")({
component: RouteComponent,
});

function RouteComponent() {
return <div>Hello "/_protected/resources/programming"!</div>;
}
9 changes: 9 additions & 0 deletions apps/web/src/routes/_protected/resources/sponsors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_protected/resources/sponsors")({
component: RouteComponent,
});

function RouteComponent() {
return <div>Hello "/_protected/resources/sponsors"!</div>;
}
21 changes: 21 additions & 0 deletions apps/web/src/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
--text-main: var(--color-zinc-900);
--text-secondary: var(--color-zinc-600);

/* NAVBAR */
--navlink-bg-inactive: transparent;
--navlink-bg-active: var(--color-neutral-300);

/* NAVBAR TEXT */
--navlink-text: var(--text-main);
--navlink-secondary-text: var(--color-neutral-500);

/* BUTTONS */
--button-primary: var(--color-blue-500);
--button-primary-hover: var(--color-blue-700);
Expand Down Expand Up @@ -120,6 +128,14 @@
--text-main: var(--color-zinc-100);
--text-secondary: var(--color-zinc-400);

/* NAVBAR */
--navlink-bg-inactive: transparent;
--navlink-bg-active: var(--surface);

/* NAVBAR TEXT */
--navlink-text: var(--text-main);
--navlink-secondary-text: var(--color-neutral-500);

/* BUTTONS */
--button-primary: var(--color-blue-500);
--button-primary-hover: var(--color-blue-700);
Expand Down Expand Up @@ -263,6 +279,11 @@
--color-text-main: var(--text-main);
--color-text-secondary: var(--text-secondary);

--color-navlink-bg-inactive: var(--navlink-bg-inactive);
--color-navlink-bg-active: var(--navlink-bg-active);
--color-navlink-text: var(--navlink-text);
--color-navlink-secondary-text: var(--navlink-secondary-text);

--color-button-primary: var(--button-primary);
--color-button-primary-hover: var(--button-primary-hover);
--color-button-primary-disabled: var(--button-primary-disabled);
Expand Down