Skip to content

Commit

Permalink
Merge pull request #259 from afspeirs/feat/add-notes-list-to-home-page
Browse files Browse the repository at this point in the history
add NotesList to HomePage
  • Loading branch information
afspeirs authored Mar 18, 2024
2 parents a542eca + 1a5b66a commit b5fb26c
Show file tree
Hide file tree
Showing 18 changed files with 240 additions and 177 deletions.
11 changes: 5 additions & 6 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef } from 'react';
import type { Ref } from 'react';
import { NavLink } from 'react-router-dom';
import { Link } from 'react-router-dom';

import { classNames } from '@/utils/classNames';
import type { ButtonProps } from './types';
Expand Down Expand Up @@ -37,14 +37,13 @@ export const Button = forwardRef(({
target = '_self',
...props
}: ButtonProps, ref) => (href ? (
<NavLink
className={({ isActive }) => classNames(
<Link
className={classNames(
iconOnly ? style.iconOnly : style.withText,
isActive ? coloursActive[colourActive] : colours[colour],
active ? coloursActive[colourActive] : colours[colour],
style.base,
className,
)}
end // This is essentially the same as the old exact prop
ref={ref as Ref<HTMLAnchorElement>}
rel={target === '_blank' ? 'noreferrer' : undefined}
target={target}
Expand All @@ -69,7 +68,7 @@ export const Button = forwardRef(({
{secondaryAction}
</div>
)}
</NavLink>
</Link>
) : (
<button
type="button"
Expand Down
6 changes: 3 additions & 3 deletions src/components/Button/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { LucideIcon } from 'lucide-react';
import type { MouseEvent, ReactNode } from 'react';
import type { To } from 'react-router-dom';

import { colours, coloursActive } from '.';

interface BaseProps {
active?: boolean,
children: ReactNode,
className?: string,
colour?: keyof typeof colours;
Expand All @@ -15,17 +17,15 @@ interface BaseProps {
}

interface ButtonOptions extends BaseProps {
active?: boolean,
disabled?: boolean,
href?: never,
onClick?: (event: MouseEvent<HTMLButtonElement>) => void
target?: never,
}

interface LinkOptions extends BaseProps {
active?: never,
disabled?: never,
href: string,
href: To,
onClick?: never,
target?: '_self' | '_blank',
}
Expand Down
69 changes: 14 additions & 55 deletions src/components/FolderList/FolderListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,31 @@
import { Disclosure } from '@headlessui/react';
import { useAtomValue } from 'jotai';
import { ChevronUpIcon, FolderClosedIcon, FolderOpenIcon } from 'lucide-react';
import { useCallback } from 'react';
import { useRxData } from 'rxdb-hooks';
import { FolderClosedIcon } from 'lucide-react';
import { useSearchParams } from 'react-router-dom';

import type { NoteDocType, NoteQuery } from '@/api/types';
import { Button } from '@/components/Button';
import { NotesList } from '@/components/NotesList'; // eslint-disable-line import/no-cycle
import { notesSearchAtom } from '@/context/notesSearch';
import { notesSortAtom, notesSortOptions } from '@/context/notesSort';
import { classNames } from '@/utils/classNames';
import type { FolderListItemProps } from './types';

export function FolderListItem({
folder,
}: FolderListItemProps) {
const search = useAtomValue(notesSearchAtom);
const sort = useAtomValue(notesSortAtom);
const notesQuery: NoteQuery = useCallback(
(collection) => collection.find({
selector: {
folder: {
$eq: folder,
},
text: {
$regex: RegExp(search, 'i'),
},
},
sort: notesSortOptions[sort].value,
}),
[folder, search, sort],
);

const { result: notes, isFetching } = useRxData<NoteDocType>('notes', notesQuery);
const [searchParams] = useSearchParams();
const searchParamsFolder = searchParams.get('folder');

return (
<li
key={folder}
className="group/folder-context-menu relative flex flex-col"
onContextMenu={(event) => event.preventDefault()}
>
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button
as={Button}
Icon={open ? FolderOpenIcon : FolderClosedIcon}
secondaryAction={(
<ChevronUpIcon
className={classNames(
'size-6 transform transition-transform',
open ? 'rotate-180' : 'rotate-90',
)}
aria-hidden="true"
/>
)}
>
{folder}
</Disclosure.Button>
<Disclosure.Panel className="w-full pl-8">
<NotesList
notes={notes}
isFetching={isFetching}
/>
</Disclosure.Panel>
</>
)}
</Disclosure>
<Button
active={folder === searchParamsFolder}
href={{
pathname: '/',
search: `folder=${folder}`,
}}
Icon={FolderClosedIcon}
>
{folder}
</Button>
</li>
);
}
40 changes: 40 additions & 0 deletions src/components/FolderList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useRef } from 'react';
import { ViewportList } from 'react-viewport-list';

import { classNames } from '@/utils/classNames';
import { FolderListItem } from './FolderListItem';
import { FolderListProps } from './types';

export function FolderList({
folders,
isFetching,
padding,
}: FolderListProps) {
const ref = useRef<HTMLUListElement | null>(null);

return (
<ul
role="list"
className={classNames(
'flex flex-col h-full overflow-y-auto overflow-x-hidden',
padding ? 'p-2' : '',
)}
ref={ref}
>
{isFetching && (
<li className="block p-3 sm:py-2">Loading...</li>
)}
{!isFetching && folders?.length === 0 && folders.length === 0 && (
<li className="block p-3 sm:py-2">No folders found</li>
)}
<ViewportList
viewportRef={ref}
items={folders}
>
{(folder) => (
<FolderListItem key={folder} folder={folder} />
)}
</ViewportList>
</ul>
);
}
6 changes: 6 additions & 0 deletions src/components/FolderList/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export type FolderListProps = {
folders: string[],
isFetching: boolean,
padding?: boolean,
};

export type FolderListItemProps = {
folder: string,
};
19 changes: 16 additions & 3 deletions src/components/NotesList/NotesListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ContextMenu from '@radix-ui/react-context-menu';
import { MoreHorizontalIcon, StarIcon } from 'lucide-react';
import { useRef } from 'react';
import { useSearchParams } from 'react-router-dom';

import { Button } from '@/components/Button';
import { getTitle } from '@/utils/getTitle';
Expand All @@ -10,6 +11,8 @@ import type { NotesProps } from './types';
export function NotesListItem({
note,
}: NotesProps) {
const [searchParams] = useSearchParams();
const searchParamsFolder = searchParams.get('folder');
const contextTriggerRef = useRef<HTMLLIElement>(null);
const contextButtonRef = useRef<HTMLButtonElement>(null);

Expand All @@ -18,9 +21,19 @@ export function NotesListItem({
<ContextMenu.Trigger asChild ref={contextTriggerRef}>
<li className="group/note-context-menu relative flex">
<Button
href={`/note/${note.id}`}
secondaryAction={note.favourite && (
<StarIcon className="size-6 flex-shrink-0 text-primary fill-primary" aria-hidden="true" />
href={{
pathname: `/note/${note.id}`,
search: `folder=${note.folder}`,
}}
secondaryAction={(
<>
{note.folder && !searchParamsFolder && (
<span className="text-light bg-dark dark:text-dark dark:bg-light px-3 py-1 -my-1 rounded-full">{note.folder}</span>
)}
{note.favourite && (
<StarIcon className="size-6 flex-shrink-0 text-primary fill-primary" aria-hidden="true" />
)}
</>
)}
>
{getTitle(note.text)}
Expand Down
24 changes: 5 additions & 19 deletions src/components/NotesList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
import { useAtomValue } from 'jotai';
import { useMemo, useRef } from 'react';
import { useRef } from 'react';
import { ViewportList } from 'react-viewport-list';

import { FolderListItem } from '@/components/FolderList/FolderListItem';
import { foldersAtom } from '@/context/folders';
import { classNames } from '@/utils/classNames';
import { NotesListItem } from './NotesListItem';
import { NotesListProps } from './types';

export function NotesList({
includeFolders,
isFetching,
notes,
padding,
}: NotesListProps) {
const ref = useRef<HTMLUListElement | null>(null);
const folders = useAtomValue(foldersAtom);

const items = useMemo(() => [
...(includeFolders ? folders : []),
...notes.filter((note) => (includeFolders ? !note.folder : true)),
], [folders, includeFolders, notes]);

return (
<ul
role="list"
Expand All @@ -39,18 +32,11 @@ export function NotesList({
)}
<ViewportList
viewportRef={ref}
items={items}
items={notes}
>
{(note) => {
if (typeof note === 'string') {
return (
<FolderListItem key={note} folder={note} />
);
}
return (
<NotesListItem key={note.id} note={note} />
);
}}
{(note) => (
<NotesListItem key={note.id} note={note} />
)}
</ViewportList>
</ul>
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/NotesList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import type { NoteDocument } from '@/api/types';
export type NotesProps = {
note: NoteDocument,
};

export type NotesListProps = {
includeFolders?: boolean,
isFetching: boolean,
notes: NoteDocument[],
padding?: boolean,
};

export type NotesContextMenuItemProps = {
children: ReactNode,
disabled?: boolean,
Expand Down
5 changes: 1 addition & 4 deletions src/components/NotesSort/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ export function NotesSort() {
<Listbox value={sort} onChange={setSort}>
{({ open }) => (
<>
<Tooltip
content="Sort Notes"
side="left"
>
<Tooltip content={`Sort by ${notesSortOptions[sort].label}`}>
<Listbox.Button
active={open}
as={Button}
Expand Down
39 changes: 30 additions & 9 deletions src/components/Page/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { useAtom } from 'jotai';
import { ChevronLeftIcon, MenuIcon } from 'lucide-react';
import { ArrowLeftIcon, ChevronLeftIcon, MenuIcon } from 'lucide-react';
import { Helmet } from 'react-helmet-async';
import { useHotkeys } from 'react-hotkeys-hook';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';

import { Button } from '@/components/Button';
import { Tooltip } from '@/components/Tooltip';
import { drawerOpenAtom } from '@/context/navigation';
import type { PageProps } from './types';

export function Page({
children,
icons,
title,
titleShow = false,
}: PageProps) {
const [open, setOpen] = useAtom(drawerOpenAtom);
const toggleOpen = () => setOpen((prevState) => !prevState);
const navigate = useNavigate();
const { pathname, search } = useLocation();

useHotkeys('ctrl+b, meta+b', toggleOpen, {
enableOnFormTags: true,
Expand All @@ -33,13 +36,31 @@ export function Page({
</Helmet>

<header className="flex gap-2 p-2">
<Button
Icon={open ? ChevronLeftIcon : MenuIcon}
iconOnly
onClick={toggleOpen}
>
{`${open ? 'Close' : 'Open'} Sidebar`}
</Button>
<Tooltip content={`${open ? 'Close' : 'Open'} Sidebar`}>
<Button
Icon={open ? ChevronLeftIcon : MenuIcon}
iconOnly
onClick={toggleOpen}
>
{`${open ? 'Close' : 'Open'} Sidebar`}
</Button>
</Tooltip>

{(pathname !== '/' || search !== '') && (
<Tooltip content="Go back">
<Button
Icon={ArrowLeftIcon}
iconOnly
onClick={() => navigate(-1)}
>
Back
</Button>
</Tooltip>
)}

{titleShow && (
<div className="ml-2 self-center font-bold text-xl">{title}</div>
)}

<div className="ml-auto" />

Expand Down
5 changes: 3 additions & 2 deletions src/components/Page/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ReactNode } from 'react';

export interface PageProps {
children: ReactNode,
icons?: ReactNode,
title?: string,
icons?: ReactNode | null,
title?: string | null,
titleShow?: boolean | null,
}
Loading

0 comments on commit b5fb26c

Please sign in to comment.