src/components/nav-feeds.tsx 3.9 K raw
1
import { ChevronRight, Pencil, Trash2 } from "lucide-react";
2
3
import {
4
	Collapsible,
5
	CollapsibleContent,
6
	CollapsibleTrigger,
7
} from "@/components/ui/collapsible";
8
import {
9
	SidebarGroup,
10
	SidebarGroupLabel,
11
	SidebarMenu,
12
	SidebarMenuButton,
13
	SidebarMenuItem,
14
	SidebarMenuSub,
15
	SidebarMenuSubButton,
16
	SidebarMenuSubItem,
17
} from "@/components/ui/sidebar";
18
import {
19
	ContextMenu,
20
	ContextMenuContent,
21
	ContextMenuItem,
22
	ContextMenuTrigger,
23
} from "@/components/ui/context-menu";
24
25
interface Feed {
26
	id: string;
27
	title: string | null;
28
	category: string | null;
29
}
30
31
interface NavFeedsProps {
32
	feeds: readonly Feed[];
33
	selectedFeedId?: string | null;
34
	onFeedSelect: (feedId: string | null) => void;
35
	onCategoryEdit?: (category: string) => void;
36
	onCategoryDelete?: (category: string) => void;
37
}
38
39
export function NavFeeds({
40
	feeds,
41
	selectedFeedId,
42
	onFeedSelect,
43
	onCategoryEdit,
44
	onCategoryDelete,
45
}: NavFeedsProps) {
46
	// Group feeds by category
47
	const feedsByCategory = feeds.reduce(
48
		(acc, feed) => {
49
			const category = feed.category || "Uncategorized";
50
			if (!acc[category]) {
51
				acc[category] = [];
52
			}
53
			acc[category].push(feed);
54
			return acc;
55
		},
56
		{} as Record<string, Feed[]>,
57
	);
58
59
	// Sort feeds within each category alphabetically by title
60
	Object.keys(feedsByCategory).forEach((category) => {
61
		feedsByCategory[category].sort((a, b) =>
62
			(a.title || "").localeCompare(b.title || ""),
63
		);
64
	});
65
66
	const categories = Object.keys(feedsByCategory).sort();
67
68
	return (
69
		<SidebarGroup>
70
			<SidebarGroupLabel>Feeds</SidebarGroupLabel>
71
			<SidebarMenu>
72
				{/* All Feeds option */}
73
				<SidebarMenuItem>
74
					<SidebarMenuButton
75
						onClick={() => onFeedSelect(null)}
76
						isActive={selectedFeedId === null}
77
					>
78
						All Feeds
79
					</SidebarMenuButton>
80
				</SidebarMenuItem>
81
82
				{/* Unread option */}
83
				<SidebarMenuItem>
84
					<SidebarMenuButton
85
						onClick={() => onFeedSelect("unread")}
86
						isActive={selectedFeedId === "unread"}
87
					>
88
						Unread
89
					</SidebarMenuButton>
90
				</SidebarMenuItem>
91
92
				{/* Categories with feeds */}
93
				{categories.map((category) => {
94
					const isUncategorized = category === "Uncategorized";
95
					const categoryTrigger = (
96
						<CollapsibleTrigger asChild>
97
							<SidebarMenuButton tooltip={category}>
98
								<span className="font-medium">{category}</span>
99
								<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
100
							</SidebarMenuButton>
101
						</CollapsibleTrigger>
102
					);
103
104
					return (
105
						<Collapsible
106
							key={category}
107
							asChild
108
							defaultOpen={true}
109
							className="group/collapsible"
110
						>
111
							<SidebarMenuItem>
112
								{isUncategorized ? (
113
									categoryTrigger
114
								) : (
115
									<ContextMenu>
116
										<ContextMenuTrigger asChild>
117
											{categoryTrigger}
118
										</ContextMenuTrigger>
119
										<ContextMenuContent>
120
											<ContextMenuItem
121
												onClick={() => onCategoryEdit?.(category)}
122
											>
123
												<Pencil className="h-4 w-4 mr-2" />
124
												Rename Category
125
											</ContextMenuItem>
126
											<ContextMenuItem
127
												variant="destructive"
128
												onClick={() => onCategoryDelete?.(category)}
129
											>
130
												<Trash2 className="h-4 w-4 mr-2" />
131
												Delete Category
132
											</ContextMenuItem>
133
										</ContextMenuContent>
134
									</ContextMenu>
135
								)}
136
								<CollapsibleContent>
137
									<SidebarMenuSub>
138
										{feedsByCategory[category].map((feed) => (
139
											<SidebarMenuSubItem key={feed.id}>
140
												<SidebarMenuSubButton
141
													onClick={() => onFeedSelect(feed.id)}
142
													isActive={selectedFeedId === feed.id}
143
												>
144
													<span>{feed.title || "Untitled"}</span>
145
												</SidebarMenuSubButton>
146
											</SidebarMenuSubItem>
147
										))}
148
									</SidebarMenuSub>
149
								</CollapsibleContent>
150
							</SidebarMenuItem>
151
						</Collapsible>
152
					);
153
				})}
154
			</SidebarMenu>
155
		</SidebarGroup>
156
	);
157
}