import { FC, MouseEventHandler, useCallback, useState, useContext, useMemo, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { Button, Text, TextField } from '@sweatpants/react'; import { LibraContext } from '../context/libra'; import { Folder } from '../icons'; import { useUrl } from '../hooks/useUrl'; import { css, cva } from '../../../styled-system/css'; import type { Entry, GroupedEntry } from '../../../api/types'; const searchableId = (id: string) => { return id.replace('__', ' ').replaceAll('--', ' ').replaceAll('-', ' ').toLowerCase(); }; export const Navigation: FC = () => { const { entries } = useContext(LibraContext); const [searchTerm, setSearchTerm] = useState(''); const empty = useMemo( () => !entries ? true : !searchChildrenEntries(entries, searchTerm.trim().toLowerCase()), [entries, searchTerm] ); if (!entries || !entries?.length) { return null; } return ( <>
{ setSearchTerm(e.target.value); }} />
); }; const searchChildrenEntries = ( entries: Array, searchTerm: string ): boolean => { return entries.reduce((acc, child) => { if (acc) { return acc; } if (child.id && searchableId(child.id).includes(searchTerm)) { return true; } if (child.children) { return searchChildrenEntries(child.children, searchTerm); } return false; }, false); }; const searchActiveEntry = (entries: Array, activeId: string): boolean => { return entries.reduce((acc, child) => { if (acc) { return acc; } if (child.id && child.id === activeId) { return true; } if (child.children) { return searchActiveEntry(child.children, activeId); } return false; }, false); }; const Item: FC< GroupedEntry & { searchTerm?: string; } > = (props) => { const { searchTerm, children, type, name, id } = props; const [open, setOpen] = useState(null); const { activeId } = useContext(LibraContext); const containsSearchItem = useMemo(() => { let contains = false; // Checks if anything is being searched if (!searchTerm) { return false; } // Checks if this folder itself matches input search if (id && searchableId(id).includes(searchTerm)) { return true; } // Checks child entries of this folder if (children) { contains = searchChildrenEntries(children, searchTerm); } return contains; }, [searchTerm, id, children]); // This only runs if `open` is null const containsActiveId = useMemo(() => { let contains = false; // Checks child entries of this folder if (children && activeId && open === null) { contains = searchActiveEntry(children, activeId); } return contains; }, [activeId, id, children, open]); // Flips `open` after determining `containsActiveId` so that open reflects its true state useEffect(() => { if (containsActiveId && open === null) { setOpen(true); } }, [containsActiveId]); if (searchTerm && !containsSearchItem) { return null; } const shouldBeOpen = open || containsSearchItem || containsActiveId; if (type === 'entry') { return ; } return (
{shouldBeOpen ? : null} {open || (containsSearchItem &&
)}
); }; const FolderItem: FC<{ items?: Array; searchTerm?: string }> = (props) => { const { items, searchTerm } = props; if (!items) return null; return items.map((child: GroupedEntry) => { return ; }); }; const EntryItem: FC & { searchTerm?: string }> = (props) => { const { id, name, searchTerm } = props; const { activeId, loadEntry } = useContext(LibraContext); const url = useUrl({ id }); const handleClick = useCallback>(() => { loadEntry?.(id); }, [id]); if (id && searchTerm && !searchableId(id).includes(searchTerm)) { return null; } const selected = activeId === id; return (
); };