Files
eranos/src/lib/sidebarItems.tsx
T

121 lines
5.8 KiB
TypeScript

import {
Bell, Search, TrendingUp, User, Bookmark, Settings, SwatchBook, Palette,
Clapperboard, BarChart3, PartyPopper, BookOpen, BookMarked, Sparkles, Blocks,
MessageSquare, Repeat2, MessageSquareMore, Mic, Smile, Bot, SmilePlus, Camera, Film, Globe, CalendarDays,
} from 'lucide-react';
import { PlanetIcon } from '@/components/icons/PlanetIcon';
import { ChestIcon } from '@/components/icons/ChestIcon';
import { CardsIcon } from '@/components/icons/CardsIcon';
// ── Types ─────────────────────────────────────────────────────────────────────
type IconComponent = React.ComponentType<{ className?: string }>;
/** A sidebar-capable item with everything needed for display and navigation. */
export interface SidebarItemDef {
/** Unique identifier stored in sidebarOrder. */
id: string;
/** Display label. */
label: string;
/** Navigation path (e.g. '/', '/notifications', '/vines'). */
path: string;
/** Icon component. */
icon: IconComponent;
/** If true, only shown when a user is logged in. */
requiresAuth?: boolean;
}
// ── Registry ──────────────────────────────────────────────────────────────────
/**
* Single source of truth for all sidebar items.
*
* Every item that can appear in the sidebar — whether it's a system page like
* "Feed" or a Nostr content type like "Vines" — lives here with a consistent
* shape. The order here is the default display order for fresh installs.
*/
export const SIDEBAR_ITEMS: SidebarItemDef[] = [
// System pages
{ id: 'feed', label: 'Feed', path: '/', icon: PlanetIcon },
{ id: 'notifications', label: 'Notifications', path: '/notifications', icon: Bell, requiresAuth: true },
{ id: 'search', label: 'Search', path: '/search', icon: Search },
{ id: 'trends', label: 'Trends', path: '/trends', icon: TrendingUp },
{ id: 'bookmarks', label: 'Bookmarks', path: '/bookmarks', icon: Bookmark, requiresAuth: true },
{ id: 'profile', label: 'Profile', path: '/profile', icon: User, requiresAuth: true },
{ id: 'settings', label: 'Settings', path: '/settings', icon: Settings },
{ id: 'theme', label: 'Vibe', path: '/settings/theme', icon: SwatchBook },
{ id: 'ai-chat', label: 'AI Chat', path: '/ai-chat', icon: Bot, requiresAuth: true },
// Content types
{ id: 'events', label: 'Events', path: '/events', icon: CalendarDays },
{ id: 'photos', label: 'Photos', path: '/photos', icon: Camera },
{ id: 'videos', label: 'Videos', path: '/videos', icon: Film },
{ id: 'articles', label: 'Articles', path: '/articles', icon: BookOpen },
{ id: 'books', label: 'Books', path: '/books', icon: BookMarked },
{ id: 'vines', label: 'Vines', path: '/vines', icon: Clapperboard },
{ id: 'webxdc', label: 'Webxdc', path: '/webxdc', icon: Blocks },
{ id: 'themes', label: 'Themes', path: '/themes', icon: Sparkles },
{ id: 'polls', label: 'Polls', path: '/polls', icon: BarChart3 },
{ id: 'packs', label: 'Follow Packs', path: '/packs', icon: PartyPopper },
{ id: 'colors', label: 'Color Moments', path: '/colors', icon: Palette },
{ id: 'decks', label: 'Magic Decks', path: '/decks', icon: CardsIcon },
{ id: 'treasures', label: 'Treasures', path: '/treasures', icon: ChestIcon },
{ id: 'emoji-packs', label: 'Emoji Packs', path: '/emoji-packs', icon: SmilePlus },
{ id: 'world', label: 'World', path: '/world', icon: Globe },
];
/** Set of all known sidebar item IDs for quick lookup. */
export const SIDEBAR_ITEM_IDS = new Set(SIDEBAR_ITEMS.map((s) => s.id));
/** Map from ID to definition for O(1) lookup. */
const SIDEBAR_ITEM_MAP = new Map(SIDEBAR_ITEMS.map((s) => [s.id, s]));
/**
* Icons for content types used outside the sidebar (e.g. ContentSettings).
* Feed-only kinds that don't have sidebar pages are included here too.
*/
export const CONTENT_KIND_ICONS: Record<string, IconComponent> = {
posts: MessageSquare,
comments: MessageSquareMore,
reposts: Repeat2,
'generic-reposts': Repeat2,
voice: Mic,
'custom-emojis': Smile,
...Object.fromEntries(SIDEBAR_ITEMS.filter((s) => s.icon).map((s) => [s.id, s.icon])),
};
// ── Lookups ───────────────────────────────────────────────────────────────────
/** Get the sidebar item definition by ID, or undefined if unknown. */
export function getSidebarItem(id: string): SidebarItemDef | undefined {
return SIDEBAR_ITEM_MAP.get(id);
}
/** Returns the icon element for a sidebar item ID at the given size. */
export function sidebarItemIcon(id: string, size = 'size-6'): React.ReactElement {
const Icon = SIDEBAR_ITEM_MAP.get(id)?.icon ?? Palette;
return <Icon className={size} />;
}
/** Lookup display label for a sidebar item ID. */
export function itemLabel(id: string): string {
return SIDEBAR_ITEM_MAP.get(id)?.label ?? id;
}
/** Lookup navigation path for a sidebar item ID. */
export function itemPath(id: string, profilePath?: string): string {
if (id === 'profile' && profilePath) return profilePath;
return SIDEBAR_ITEM_MAP.get(id)?.path ?? `/${id}`;
}
/** Check if a sidebar item is active given the current location. */
export function isItemActive(id: string, pathname: string, _search: string, profilePath?: string): boolean {
if (id === 'profile') return !!profilePath && pathname === profilePath;
// Settings matches /settings/* but not /settings/theme (theme has its own item).
if (id === 'settings') return pathname.startsWith('/settings') && pathname !== '/settings/theme';
const item = SIDEBAR_ITEM_MAP.get(id);
if (!item) return pathname === `/${id}`;
return pathname === item.path;
}