Skip to content

Commit 910d54c

Browse files
Merge pull request #6 from florianbussmann/feat/navigation
feat: category navigation
2 parents a98734c + 2ef1486 commit 910d54c

File tree

9 files changed

+514
-15
lines changed

9 files changed

+514
-15
lines changed

app/f/[category]/[id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export async function generateStaticParams() {
1010
export default async function TrackPage({
1111
params,
1212
}: {
13-
params: Promise<{ name: string; id: string }>;
13+
params: Promise<{ category: string; id: string }>;
1414
}) {
1515
const id = (await params).id;
1616

app/f/[category]/page.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { loadAudioFiles } from "@/lib/loadAudioFiles";
22
import { categories } from "@/data/categories";
3+
import { NavMenu } from '@/components/menu';
34
import Link from "next/link";
45

5-
66
export function generateStaticParams() {
77
return categories;
88
}
@@ -14,19 +14,20 @@ export default async function CategoryPage({
1414
}) {
1515
const audioFiles = await loadAudioFiles();
1616

17-
let { category } = await params;
18-
let filteredAudioFiles = category == "all" ? audioFiles : audioFiles.filter(audio => audio.categories.includes(category));
17+
const { category } = await params;
18+
const filteredAudioFiles = category == "all" ? audioFiles : audioFiles.filter(audio => audio.categories.includes(category));
1919

2020
return (
21-
<div className="flex h-screen">
22-
<div className="grow overflow-hidden border-r border-gray-200"><div className="flex h-[70px] items-center justify-between border-b border-gray-200 p-4">
23-
<div className="flex items-center">
24-
<h1 className="flex items-center text-xl font-semibold capitalize">
25-
{category == "all" ? "Training Library" : categories.find((cat => cat.id == category))?.title}
26-
<span className="ml-2 text-sm text-gray-400">{filteredAudioFiles.length}</span>
27-
</h1>
21+
<div className="grow overflow-hidden border-r border-gray-200">
22+
<div className="flex h-[70px] items-center justify-between border-b border-gray-200 p-4">
23+
<div className="flex items-center">
24+
<NavMenu />
25+
<h1 className="flex items-center text-xl font-semibold capitalize">
26+
{category == "all" ? "Training Library" : categories.find((cat => cat.id == category))?.title}
27+
<span className="ml-2 text-sm text-gray-400">{filteredAudioFiles.length}</span>
28+
</h1>
29+
</div>
2830
</div>
29-
</div>
3031
<div className="h-[calc(100vh-64px)] overflow-auto">
3132
{filteredAudioFiles.map((audio) => {
3233
return (
@@ -64,6 +65,5 @@ export default async function CategoryPage({
6465
})}
6566
</div>
6667
</div>
67-
</div>
6868
);
6969
}

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function RootLayout({
2727
<body
2828
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
2929
>
30-
{children}
30+
<main className="grow overflow-hidden">{children}</main>
3131
</body>
3232
</html>
3333
);

components/left-sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useParams } from 'next/navigation';
77
import { Suspense } from 'react';
88

99
function BackButton() {
10-
let { category } = useParams();
10+
const { category } = useParams();
1111

1212
return (
1313
<Link href={`/f/${category}`} passHref>

components/menu.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
Sheet,
3+
SheetContent,
4+
SheetTitle,
5+
SheetTrigger,
6+
} from '@/components/ui/sheet';
7+
import { AudioWaveform, Menu } from 'lucide-react';
8+
import { categories } from "@/data/categories";
9+
import { loadAudioFiles } from "@/lib/loadAudioFiles";
10+
import Link from 'next/link';
11+
12+
export async function NavMenu() {
13+
const audioFiles = await loadAudioFiles();
14+
15+
return (
16+
<Sheet>
17+
<SheetTrigger asChild>
18+
<button className="mr-2 -ml-1 cursor-pointer rounded-full p-2 hover:bg-gray-100">
19+
<Menu size={20} />
20+
</button>
21+
</SheetTrigger>
22+
<SheetContent
23+
side="left"
24+
className="p-6 w-[300px] transition-transform duration-200 ease-out data-[state=open]:duration-200 data-[state=open]:ease-out sm:w-[400px]"
25+
>
26+
<SheetTitle>Menu</SheetTitle>
27+
<nav className="mt-4 flex flex-col space-y-4">
28+
{categories.map((category) => {
29+
return (
30+
<Link
31+
key={category.id}
32+
href={`/f/${category.id}`}
33+
className="flex items-center space-x-2 rounded p-2 text-gray-700 hover:bg-gray-100"
34+
>
35+
<AudioWaveform size={20} />
36+
<div className="flex justify-between w-full">
37+
<span>{category.title}</span>
38+
<span className="text-sm text-gray-400">
39+
{(category.id == "all" ? audioFiles : audioFiles.filter(audio => audio.categories.includes(category.id))).length}
40+
</span></div>
41+
</Link>
42+
);
43+
})}
44+
</nav>
45+
</SheetContent>
46+
</Sheet>
47+
);
48+
}

components/ui/sheet.tsx

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import * as SheetPrimitive from "@radix-ui/react-dialog"
5+
import { XIcon } from "lucide-react"
6+
7+
import { cn } from "@/lib/utils"
8+
9+
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
10+
return <SheetPrimitive.Root data-slot="sheet" {...props} />
11+
}
12+
13+
function SheetTrigger({
14+
...props
15+
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
16+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
17+
}
18+
19+
function SheetClose({
20+
...props
21+
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
22+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
23+
}
24+
25+
function SheetPortal({
26+
...props
27+
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
28+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
29+
}
30+
31+
function SheetOverlay({
32+
className,
33+
...props
34+
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
35+
return (
36+
<SheetPrimitive.Overlay
37+
data-slot="sheet-overlay"
38+
className={cn(
39+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
40+
className
41+
)}
42+
{...props}
43+
/>
44+
)
45+
}
46+
47+
function SheetContent({
48+
className,
49+
children,
50+
side = "right",
51+
...props
52+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
53+
side?: "top" | "right" | "bottom" | "left"
54+
}) {
55+
return (
56+
<SheetPortal>
57+
<SheetOverlay />
58+
<SheetPrimitive.Content
59+
data-slot="sheet-content"
60+
className={cn(
61+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
62+
side === "right" &&
63+
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
64+
side === "left" &&
65+
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
66+
side === "top" &&
67+
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
68+
side === "bottom" &&
69+
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
70+
className
71+
)}
72+
{...props}
73+
>
74+
{children}
75+
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
76+
<XIcon className="size-4" />
77+
<span className="sr-only">Close</span>
78+
</SheetPrimitive.Close>
79+
</SheetPrimitive.Content>
80+
</SheetPortal>
81+
)
82+
}
83+
84+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
85+
return (
86+
<div
87+
data-slot="sheet-header"
88+
className={cn("flex flex-col gap-1.5 p-4", className)}
89+
{...props}
90+
/>
91+
)
92+
}
93+
94+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
95+
return (
96+
<div
97+
data-slot="sheet-footer"
98+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
99+
{...props}
100+
/>
101+
)
102+
}
103+
104+
function SheetTitle({
105+
className,
106+
...props
107+
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
108+
return (
109+
<SheetPrimitive.Title
110+
data-slot="sheet-title"
111+
className={cn("text-foreground font-semibold", className)}
112+
{...props}
113+
/>
114+
)
115+
}
116+
117+
function SheetDescription({
118+
className,
119+
...props
120+
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
121+
return (
122+
<SheetPrimitive.Description
123+
data-slot="sheet-description"
124+
className={cn("text-muted-foreground text-sm", className)}
125+
{...props}
126+
/>
127+
)
128+
}
129+
130+
export {
131+
Sheet,
132+
SheetTrigger,
133+
SheetClose,
134+
SheetContent,
135+
SheetHeader,
136+
SheetFooter,
137+
SheetTitle,
138+
SheetDescription,
139+
}

data/categories.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export interface Category {
44
}
55

66
export const categories: Category[] = [
7+
{
8+
id: "all",
9+
title: "Training Library",
10+
},
711
{
812
id: "fit",
913
title: "Health & Fitness",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"lint": "eslint"
1010
},
1111
"dependencies": {
12+
"@radix-ui/react-dialog": "^1.1.15",
1213
"@radix-ui/react-slider": "^1.3.6",
1314
"@radix-ui/react-slot": "^1.2.3",
1415
"class-variance-authority": "^0.7.1",

0 commit comments

Comments
 (0)