Skip to content

Commit 4431b65

Browse files
author
Sandi Karajic
committed
add breadcrumbs
1 parent 499e461 commit 4431b65

24 files changed

Lines changed: 1025 additions & 2096 deletions

components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://ui.shadcn.com/schema.json",
3-
"style": "new-york",
3+
"style": "base-vega",
44
"rsc": false,
55
"tsx": true,
66
"tailwind": {

index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
<link rel="icon" href="/favicon.ico" type="image/x-icon">
88
<meta name="description" content="A community made to manage a custom API that serves static data.">
99
<title>RAW - CommunityDragon</title>
10-
<script prerender type="module" src="/src/index.tsx"></script>
10+
<script type="module" src="/src/theme.ts"></script>
1111
</head>
1212
<body class="group/body overscroll-none antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)] theme-default">
1313
<div id="app"></div>
1414
<template id="table-index">
1515
{{index}}
1616
</template>
17+
<script prerender type="module" src="/src/index.tsx"></script>
1718
</body>
1819
</html>

package-lock.json

Lines changed: 166 additions & 1805 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"preview": "vite preview"
88
},
99
"dependencies": {
10+
"@base-ui/react": "^1.2.0",
1011
"@fontsource-variable/inter": "^5.2.8",
1112
"@tailwindcss/vite": "^4.2.1",
1213
"class-variance-authority": "^0.7.1",
@@ -15,7 +16,6 @@
1516
"preact": "^10.26.9",
1617
"preact-iso": "^2.11.1",
1718
"preact-render-to-string": "^6.6.6",
18-
"radix-ui": "^1.4.3",
1919
"tailwind-merge": "^3.5.0",
2020
"tailwindcss": "^4.2.1"
2121
},

src/app.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,27 @@ import { FileTable } from '@components/file-table';
66
import './style.css';
77
import { ThemeProvider } from '@components/theme-provider';
88
import { NavBar } from '@components/nav-bar';
9+
import { Card, CardContent, CardHeader } from '@components/ui/card';
10+
import { PathBreadcrumbs } from '@components/path-breadcrumbs';
911

1012
interface AppProps {
11-
currentPath: string;
13+
path: string;
1214
files: FileEntry[];
1315
}
1416

15-
export const App: FunctionalComponent<AppProps> = ({ currentPath, files }) => (
17+
export const App: FunctionalComponent<AppProps> = ({ path, files }) => (
1618
<ThemeProvider storageKey="ui-theme">
1719
<NavBar />
1820
<div className="relative">
19-
<div className="flex flex-col gap-4 max-w-4xl m-auto p-4 pb-12">
20-
{currentPath && (
21-
<div className="current-path">
22-
<strong>Current directory:</strong> {currentPath}
23-
</div>
24-
)}
25-
26-
<FileTable files={files} />
21+
<div className="flex flex-col gap-4 max-w-5xl m-auto p-4 pb-12">
22+
<Card>
23+
<CardHeader>
24+
<PathBreadcrumbs path={path ?? '/'} />
25+
</CardHeader>
26+
<CardContent>
27+
<FileTable files={files} />
28+
</CardContent>
29+
</Card>
2730
</div>
2831
</div>
2932
</ThemeProvider>

src/components/file-table.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ interface Props {
1818
}
1919

2020
export const FileTable: React.FC<Props> = ({ files }) => {
21-
const [sortColumn, setSortColumn] = useState<SortColumn>('name');
22-
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
21+
// Start with no sorting
22+
const [sortColumn, setSortColumn] = useState<SortColumn | null>(null);
23+
const [sortDirection, setSortDirection] = useState<SortDirection>('asc'); // only used when sortColumn is not null
2324

2425
const sortedFiles = useMemo(
2526
() => sortFiles(files, sortColumn, sortDirection),
@@ -28,14 +29,21 @@ export const FileTable: React.FC<Props> = ({ files }) => {
2829

2930
const toggleSort = (column: SortColumn) => {
3031
if (sortColumn === column) {
31-
setSortDirection(prev => (prev === 'asc' ? 'desc' : 'asc'));
32+
// Same column: cycle asc → desc → unsorted
33+
if (sortDirection === 'asc') {
34+
setSortDirection('desc');
35+
} else if (sortDirection === 'desc') {
36+
setSortColumn(null); // go to unsorted
37+
// direction can stay as 'desc' or be reset; it's ignored when column is null
38+
}
3239
} else {
40+
// Different column: set to asc
3341
setSortColumn(column);
3442
setSortDirection('asc');
3543
}
3644
};
3745

38-
const formatDate = (date: Date|null) => {
46+
const formatDate = (date: Date | null) => {
3947
if (!date) return '-';
4048
const year = date.getFullYear();
4149
const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -44,33 +52,37 @@ export const FileTable: React.FC<Props> = ({ files }) => {
4452
};
4553

4654
const SortIcon = ({ column }: { column: SortColumn }) => {
47-
if (sortColumn !== column) return <ArrowUpDown className="ml-2 h-4 w-4" />;
48-
return sortDirection === 'asc'
49-
? <ArrowUp className="ml-2 h-4 w-4" />
50-
: <ArrowDown className="ml-2 h-4 w-4" />;
55+
if (sortColumn !== column) {
56+
return <ArrowUpDown className="ml-2 h-4 w-4" />;
57+
}
58+
return sortDirection === 'asc' ? (
59+
<ArrowUp className="ml-2 h-4 w-4" />
60+
) : (
61+
<ArrowDown className="ml-2 h-4 w-4" />
62+
);
5163
};
5264

5365
return (
54-
<div className="rounded-md border">
66+
<div className="rounded-md overflow-hidden">
5567
<Table>
5668
<TableHeader>
5769
<TableRow>
5870
<TableHead className="w-full">
59-
<Button variant="ghost" size='sm' onClick={() => toggleSort('name')}>
71+
<Button variant="ghost" size="sm" onClick={() => toggleSort('name')}>
6072
Name
6173
<SortIcon column="name" />
6274
</Button>
6375
</TableHead>
64-
76+
6577
<TableHead className="text-right whitespace-nowrap w-min">
66-
<Button variant="ghost" size='sm' onClick={() => toggleSort('size')}>
78+
<Button variant="ghost" size="sm" onClick={() => toggleSort('size')}>
6779
Size
6880
<SortIcon column="size" />
6981
</Button>
7082
</TableHead>
71-
83+
7284
<TableHead className="text-right whitespace-nowrap w-min">
73-
<Button variant="ghost" size='sm' onClick={() => toggleSort('date')}>
85+
<Button variant="ghost" size="sm" onClick={() => toggleSort('date')}>
7486
Modified
7587
<SortIcon column="date" />
7688
</Button>

src/components/nav-bar.tsx

Lines changed: 18 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,6 @@ import logo from '@assets/logo-small.png';
1414
import { Button } from "./ui/button";
1515
import { HandHeart } from "lucide-react";
1616

17-
const components: { title: string; href: string; description: string }[] = [
18-
{
19-
title: "Alert Dialog",
20-
href: "/docs/primitives/alert-dialog",
21-
description:
22-
"A modal dialog that interrupts the user with important content and expects a response.",
23-
},
24-
{
25-
title: "Hover Card",
26-
href: "/docs/primitives/hover-card",
27-
description:
28-
"For sighted users to preview content available behind a link.",
29-
},
30-
{
31-
title: "Progress",
32-
href: "/docs/primitives/progress",
33-
description:
34-
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
35-
},
36-
{
37-
title: "Scroll-area",
38-
href: "/docs/primitives/scroll-area",
39-
description: "Visually or semantically separates content.",
40-
},
41-
{
42-
title: "Tabs",
43-
href: "/docs/primitives/tabs",
44-
description:
45-
"A set of layered sections of content—known as tab panels—that are displayed one at a time.",
46-
},
47-
{
48-
title: "Tooltip",
49-
href: "/docs/primitives/tooltip",
50-
description:
51-
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
52-
},
53-
]
54-
5517
export const NavBar: React.FC = () => {
5618
return (
5719
<header className="bg-background sticky top-0 z-50 w-full">
@@ -68,9 +30,10 @@ export const NavBar: React.FC = () => {
6830
<NavigationMenu>
6931
<NavigationMenuList>
7032
<NavigationMenuItem>
71-
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
72-
<a href="https://www.communitydragon.org">Home</a>
73-
</NavigationMenuLink>
33+
<NavigationMenuLink
34+
className={navigationMenuTriggerStyle()}
35+
render={<a href="https://www.communitydragon.org">Home</a>}
36+
/>
7437
</NavigationMenuItem>
7538

7639
<NavigationMenuItem>
@@ -124,34 +87,23 @@ export const NavBar: React.FC = () => {
12487
</ul>
12588
</NavigationMenuContent>
12689
</NavigationMenuItem>
127-
{/* <NavigationMenuItem className="hidden md:flex">
128-
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
129-
<NavigationMenuContent>
130-
<ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
131-
{components.map((component) => (
132-
<ListItem
133-
key={component.title}
134-
title={component.title}
135-
href={component.href}
136-
>
137-
{component.description}
138-
</ListItem>
139-
))}
140-
</ul>
141-
</NavigationMenuContent>
142-
</NavigationMenuItem> */}
14390
</NavigationMenuList>
14491
</NavigationMenu>
14592
</div>
14693

14794
<NavigationMenu>
14895
<NavigationMenuList>
14996
<NavigationMenuItem>
150-
<Button variant="secondary" size="sm" asChild>
151-
<a href="https://www.patreon.com/communitydragon">
152-
Support us <HandHeart />
153-
</a>
154-
</Button>
97+
<Button
98+
variant="secondary"
99+
size="sm"
100+
nativeButton={false}
101+
render={
102+
<a href="https://www.patreon.com/communitydragon">
103+
Support us <HandHeart />
104+
</a>
105+
}
106+
/>
155107
</NavigationMenuItem>
156108
</NavigationMenuList>
157109
</NavigationMenu>
@@ -169,14 +121,10 @@ function ListItem({
169121
}: React.ComponentPropsWithoutRef<"li"> & { href: string }) {
170122
return (
171123
<li {...props}>
172-
<NavigationMenuLink asChild>
173-
<a href={href}>
174-
<div className="flex flex-col gap-1 text-sm">
175-
<div className="leading-none font-medium">{title}</div>
176-
<div className="text-muted-foreground line-clamp-2">{children}</div>
177-
</div>
178-
</a>
179-
</NavigationMenuLink>
124+
<NavigationMenuLink render={<a href={href}><div className="flex flex-col gap-1 text-sm">
125+
<div className="leading-none font-medium">{title}</div>
126+
<div className="text-muted-foreground line-clamp-2">{children}</div>
127+
</div></a>} />
180128
</li>
181129
)
182130
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from "react"
2+
import {
3+
Breadcrumb,
4+
BreadcrumbEllipsis,
5+
BreadcrumbItem,
6+
BreadcrumbLink,
7+
BreadcrumbList,
8+
BreadcrumbPage,
9+
BreadcrumbSeparator,
10+
} from "@/components/ui/breadcrumb"
11+
import {
12+
DropdownMenu,
13+
DropdownMenuContent,
14+
DropdownMenuGroup,
15+
DropdownMenuItem,
16+
DropdownMenuTrigger,
17+
} from "@/components/ui/dropdown-menu"
18+
import { Button } from "@/components/ui/button"
19+
20+
interface Props {
21+
path: string
22+
}
23+
24+
type crumb = {
25+
label: string;
26+
href: string;
27+
}
28+
29+
export function PathBreadcrumbs({ path }: Props) {
30+
const segments = path.split("/").filter((segment) => segment !== "")
31+
32+
const breadcrumbs: crumb[] = segments.map((segment, index) => {
33+
const href = `/${segments.slice(0, index + 1).join("/")}/`
34+
return {
35+
label: segment,
36+
href,
37+
}
38+
})
39+
40+
const allItems = [{ label: "Home", href: "/" }, ...breadcrumbs]
41+
42+
const maxVisible = 6
43+
const isTooLong = allItems.length > maxVisible
44+
45+
const itemsToDisplay = isTooLong
46+
? [...allItems.slice(0, maxVisible - 2), allItems.slice(maxVisible - 2, allItems.length - 2), allItems[allItems.length - 1]]
47+
: allItems
48+
49+
return (
50+
<Breadcrumb>
51+
<BreadcrumbList>
52+
{itemsToDisplay.map((item, i) => (
53+
<>
54+
{i !== 0 ? (
55+
<BreadcrumbSeparator />
56+
) : null}
57+
<BreadcrumbItem>
58+
{Array.isArray(item) ? (
59+
<DropdownMenu>
60+
<DropdownMenuTrigger nativeButton={false} render={
61+
<span>
62+
<Button size="icon-sm" variant="ghost">
63+
<BreadcrumbEllipsis />
64+
<span className="sr-only">Toggle menu</span>
65+
</Button>
66+
</span>
67+
}/>
68+
<DropdownMenuContent align="start" style={{'--anchor-width': 'auto',}} className='text-nowrap'>
69+
<DropdownMenuGroup>
70+
{item.map((subItem) => (
71+
<DropdownMenuItem render={
72+
<a href={subItem.href}>{subItem.label}</a>
73+
}/>
74+
))}
75+
</DropdownMenuGroup>
76+
</DropdownMenuContent>
77+
</DropdownMenu>
78+
) : (
79+
<BreadcrumbLink render={<a href={item.href}>{item.label}</a>} />
80+
)}
81+
</BreadcrumbItem>
82+
</>
83+
))}
84+
</BreadcrumbList>
85+
</Breadcrumb>
86+
)
87+
}

0 commit comments

Comments
 (0)