From 0b5279624e1c8af38e61ece6a11aa3b2cfc26e86 Mon Sep 17 00:00:00 2001 From: Christopher Loverich <1010084+cloverich@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:55:45 -0700 Subject: [PATCH] clean-up sidebar / more Tags usage - clean-up sidebar styling (consistent headings, real tags, remove evergreen components) - more abstraction around Tag component (variants, etc) - abstract Collapse - replace Badge with Tag in documents index view --- src/components/Collapse.tsx | 32 ++++++++ src/components/Sidesheet.tsx | 2 +- src/components/TagInput.tsx | 89 ++++++++++++++++++--- src/views/documents/DocumentItem.tsx | 33 ++++---- src/views/documents/SearchStore.ts | 1 + src/views/documents/sidebar/JournalItem.tsx | 23 +----- src/views/documents/sidebar/Sidebar.tsx | 34 ++++---- src/views/documents/sidebar/TagsList.tsx | 36 ++++----- src/views/edit/FrontMatter.tsx | 1 + 9 files changed, 158 insertions(+), 93 deletions(-) create mode 100644 src/components/Collapse.tsx diff --git a/src/components/Collapse.tsx b/src/components/Collapse.tsx new file mode 100644 index 0000000..c2e8b52 --- /dev/null +++ b/src/components/Collapse.tsx @@ -0,0 +1,32 @@ +import React from "react"; + +import { Icons } from "./icons"; + +type CollapseProps = React.PropsWithChildren<{ + defaultOpen?: boolean; + heading: string; +}>; + +/** + * Collapse component that can be toggled open and closed. + */ +export function Collapse({ heading, defaultOpen, children }: CollapseProps) { + const [isOpen, setIsOpen] = React.useState( + defaultOpen == null ? false : defaultOpen, + ); + + const Icon = isOpen ? Icons.chevronDown : Icons.chevronRight; + + return ( +
+
setIsOpen(!isOpen)} + > + {heading} + +
+ {isOpen && children} +
+ ); +} diff --git a/src/components/Sidesheet.tsx b/src/components/Sidesheet.tsx index 420cb23..a2b2182 100644 --- a/src/components/Sidesheet.tsx +++ b/src/components/Sidesheet.tsx @@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "drag-none fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-300", + "drag-none fixed z-50 gap-4 bg-background p-4 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-300", { variants: { side: { diff --git a/src/components/TagInput.tsx b/src/components/TagInput.tsx index a8528d5..fc7c02d 100644 --- a/src/components/TagInput.tsx +++ b/src/components/TagInput.tsx @@ -1,4 +1,5 @@ import { cn } from "@udecode/cn"; +import { cva } from "class-variance-authority"; import { observable } from "mobx"; import { observer } from "mobx-react-lite"; import * as React from "react"; @@ -16,12 +17,18 @@ interface TagInputProps { placeholder?: string; /** When true, hide the borders / disable padding */ ghost?: boolean; + /** A lazy hack to make the editors tags always start with a hash */ + prefixHash?: boolean; } +/** + * A multi-select input where values appear as tags + */ const TagInput = observer((props: TagInputProps) => { const inputRef = React.useRef(null); const containerRef = React.useRef(null); const [dropdown, _] = React.useState(observable({ open: false })); + const hash = props.prefixHash ? "#" : null; // Close the typeahead menu when clicking outside of the dropdown React.useEffect(() => { @@ -53,7 +60,10 @@ const TagInput = observer((props: TagInputProps) => { )} > {props.tokens.map((token, idx) => ( - + props.onRemove(token)}> + {hash} + {token} + ))} { export default TagInput; -interface TagProps { - token: string; - remove: (token: string) => void; -} +const tagVariants = cva( + cn( + "mr-2 flex flex-shrink cursor-pointer items-center overflow-hidden text-ellipsis whitespace-nowrap rounded-sm border border-slate-800 bg-violet-200 px-1 py-0.5 text-xs text-slate-600", + ), + { + variants: { + variant: { + default: "", + muted: "border-default bg-accent", + }, + size: { + default: "h-10 px-4 py-2", + xs: "py-0 px-0.5 text-xs", + sm: "h-7 px-2", + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, + }, +); + +type PTag = React.PropsWithChildren<{ + className?: string; + onClick?: () => void; + size?: "default" | "xs" | "sm"; + variant?: "default" | "muted"; +}>; -const Tag = ({ token, remove }: TagProps) => { +const Tag = ({ size, className, children, onClick, variant }: PTag) => { return ( - - {token} - - + ); }; diff --git a/src/views/documents/DocumentItem.tsx b/src/views/documents/DocumentItem.tsx index 344d0a2..99308cf 100644 --- a/src/views/documents/DocumentItem.tsx +++ b/src/views/documents/DocumentItem.tsx @@ -1,6 +1,6 @@ -import { Badge } from "evergreen-ui"; import React from "react"; -import { SearchItem } from "./SearchStore"; +import { ClickableTag } from "../../components/TagInput"; +import { SearchItem, useSearchStore } from "./SearchStore"; export function DocumentItem(props: { doc: SearchItem; @@ -8,30 +8,27 @@ export function DocumentItem(props: { getName: (id: string) => string; }) { const { doc, edit, getName } = props; + const search = useSearchStore()!; return ( -
edit(doc.id)} - className="hover:underline-offset flex cursor-pointer hover:underline" - > +
{/* Without mono font, dates won't be a uniform width */}
{doc.createdAt.slice(0, 10)}
-
+
edit(doc.id)} + > {doc.title} - - - {getName(doc.journalId)} - -
+ search.addToken(`in:${getName(doc.journalId)}`)} + > + in:{getName(doc.journalId)} +
); } diff --git a/src/views/documents/SearchStore.ts b/src/views/documents/SearchStore.ts index 7eb8f16..8ec6208 100644 --- a/src/views/documents/SearchStore.ts +++ b/src/views/documents/SearchStore.ts @@ -207,6 +207,7 @@ export class SearchStore { @action addToken = (searchStr: string, resetPagination = true) => { const token = this.parser.parseToken(searchStr); + console.log("token:", token); // todo: only search if the token string changes if (token) { diff --git a/src/views/documents/sidebar/JournalItem.tsx b/src/views/documents/sidebar/JournalItem.tsx index 218586c..66d81b5 100644 --- a/src/views/documents/sidebar/JournalItem.tsx +++ b/src/views/documents/sidebar/JournalItem.tsx @@ -1,6 +1,6 @@ import * as ContextMenu from "@radix-ui/react-context-menu"; import { cn } from "@udecode/cn"; -import { Heading, Pane, toaster } from "evergreen-ui"; +import { toaster } from "evergreen-ui"; import { noop } from "lodash"; import { observer } from "mobx-react-lite"; import React, { useContext } from "react"; @@ -11,27 +11,6 @@ import { useIsMounted } from "../../../hooks/useIsMounted"; import { JournalsStoreContext } from "../../../hooks/useJournalsLoader"; import { SidebarStore } from "./store"; -/** - * Collapse component that can be toggled open and closed. - */ -export function Collapse(props: { defaultOpen?: boolean; children: any }) { - const [isOpen, setIsOpen] = React.useState( - props.defaultOpen == null ? false : props.defaultOpen, - ); - - const Icon = isOpen ? Icons.chevronDown : Icons.chevronRight; - - return ( - - setIsOpen(!isOpen)} cursor="pointer"> - Archived Journals - - - {isOpen && props.children} - - ); -} - export function JournalCreateForm({ done }: { done: () => any }) { const [journal, _] = React.useState<{ name: string }>({ name: "My new journal", diff --git a/src/views/documents/sidebar/Sidebar.tsx b/src/views/documents/sidebar/Sidebar.tsx index 4198ae2..1b77bee 100644 --- a/src/views/documents/sidebar/Sidebar.tsx +++ b/src/views/documents/sidebar/Sidebar.tsx @@ -1,9 +1,10 @@ import { Root as VisuallyHidden } from "@radix-ui/react-visually-hidden"; import React from "react"; -import { Card, Heading, IconButton, Pane, PlusIcon } from "evergreen-ui"; +import { IconButton, PlusIcon } from "evergreen-ui"; import { observer } from "mobx-react-lite"; +import { Collapse } from "../../../components/Collapse"; import { Sheet, SheetContent, @@ -12,7 +13,7 @@ import { SheetTitle, } from "../../../components/Sidesheet"; import { SearchStore } from "../SearchStore"; -import { Collapse, JournalCreateForm, JournalItem } from "./JournalItem"; +import { JournalCreateForm, JournalItem } from "./JournalItem"; import { TagsList } from "./TagsList"; import { SidebarStore, useSidebarStore } from "./store"; @@ -60,27 +61,22 @@ export default observer(function JournalSelectionSidebar(props: SidebarProps) { const InnerContent = observer(({ store }: { store: SidebarStore }) => { return ( - <> - {" "} - - - - Active Journals{" "} +
+
+
+
+ Active Journals Add Journal - - +
+
    {store.adding && (
  • @@ -102,8 +98,10 @@ const InnerContent = observer(({ store }: { store: SidebarStore }) => { ); })}
+
- +
+
    {store.journalStore.archived.map((j) => { return ( @@ -120,8 +118,8 @@ const InnerContent = observer(({ store }: { store: SidebarStore }) => { })}
- +
- +
); }); diff --git a/src/views/documents/sidebar/TagsList.tsx b/src/views/documents/sidebar/TagsList.tsx index 95e7729..2fa52e6 100644 --- a/src/views/documents/sidebar/TagsList.tsx +++ b/src/views/documents/sidebar/TagsList.tsx @@ -1,12 +1,6 @@ -import { - Card, - FolderCloseIcon, - Heading, - ListItem, - UnorderedList, -} from "evergreen-ui"; import React from "react"; +import { ClickableTag as Tag } from "../../../components/TagInput"; import { useTags } from "../../../hooks/useTags"; /** @@ -24,20 +18,20 @@ export function TagsList(props: { search: (tag: string) => boolean }) { return "error loading tags"; } - const tagItems = tags.map((t) => { - return ( - - props.search(t)}> - {t} - - - ); - }); - return ( - - Tags - {tagItems} - +
+
+ Tags +
+
+ {tags.map((t) => { + return ( + props.search(t)}> + #{t} + + ); + })} +
+
); } diff --git a/src/views/edit/FrontMatter.tsx b/src/views/edit/FrontMatter.tsx index 64c7478..25b24ed 100644 --- a/src/views/edit/FrontMatter.tsx +++ b/src/views/edit/FrontMatter.tsx @@ -143,6 +143,7 @@ const FrontMatter = observer( onRemove={onRemoveTag} placeholder="Add tags" ghost={true} + prefixHash={true} />