From 875ce2603cd884c5f9c92a3a4da9fb547d042d27 Mon Sep 17 00:00:00 2001 From: isqua Date: Sat, 28 Oct 2023 12:05:10 +0300 Subject: [PATCH] docs: add some inline docs to explain some points --- src/app/Router/TestRouter.tsx | 1 + src/components/Article/Article.tsx | 2 ++ src/components/Input/Input.tsx | 1 + src/components/Transitions/HeightTransition.tsx | 4 ++++ src/features/toc/api/useGetTocQuery.ts | 1 + src/features/toc/core/buildMenuSection.ts | 16 ++++++++++++++++ src/features/toc/core/filterTreeNodes.ts | 8 ++++++++ src/features/toc/core/getBreadCrumbs.ts | 5 +++++ src/features/toc/core/isTextMatch.ts | 7 +++++++ .../toc/ui/Menu/Context/MenuProvider.tsx | 3 +++ src/features/toc/ui/Menu/Context/hooks.ts | 4 ++++ src/features/toc/ui/Menu/Item/Item.tsx | 12 ++++++++++++ src/features/toc/ui/Menu/Menu.tsx | 3 +++ src/features/toc/ui/Menu/Section/Section.tsx | 4 ++++ src/hooks/useCurrentPageUrl.ts | 4 ++++ src/test/render.tsx | 1 + 16 files changed, 76 insertions(+) diff --git a/src/app/Router/TestRouter.tsx b/src/app/Router/TestRouter.tsx index 1446e72..5092b64 100644 --- a/src/app/Router/TestRouter.tsx +++ b/src/app/Router/TestRouter.tsx @@ -2,6 +2,7 @@ import { PropsWithChildren } from 'react' import { RouterProvider, createMemoryRouter } from 'react-router-dom' type TestRouterProps = PropsWithChildren<{ + /** Current URL */ url?: string }> diff --git a/src/components/Article/Article.tsx b/src/components/Article/Article.tsx index e650d39..240a768 100644 --- a/src/components/Article/Article.tsx +++ b/src/components/Article/Article.tsx @@ -4,7 +4,9 @@ import { BreadCrumbs } from '../BreadCrumbs' import styles from './Article.module.css' type ArticleProps = { + /** A page to display */ page: PageDescriptor + /** Array of its ancestors including the page itself */ breadcrumbs: PageDescriptor[] } diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index 9c5090f..0cacc3a 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -5,6 +5,7 @@ import styles from './Input.module.css' type InputProps = { onChange?: (text: string) => void placeholder?: string + /** Should show spinner */ isLoading?: boolean } diff --git a/src/components/Transitions/HeightTransition.tsx b/src/components/Transitions/HeightTransition.tsx index f8af55f..4131fdf 100644 --- a/src/components/Transitions/HeightTransition.tsx +++ b/src/components/Transitions/HeightTransition.tsx @@ -3,9 +3,13 @@ import { CSSTransition } from 'react-transition-group' import type { CSSTransitionClassNames } from 'react-transition-group/CSSTransition' type HeightTransitionProps = PropsWithChildren<{ + /** Show the component; triggers the enter or exit states */ isVisible: boolean + /** A React reference to DOM element that need to transition */ nodeRef: RefObject + /** The animation `classNames` applied to the component as it enters or exits */ classNames: CSSTransitionClassNames + /** Minimum expected height of the element, in case the element is not visible */ minHeight: number }> diff --git a/src/features/toc/api/useGetTocQuery.ts b/src/features/toc/api/useGetTocQuery.ts index 13cca93..41072ff 100644 --- a/src/features/toc/api/useGetTocQuery.ts +++ b/src/features/toc/api/useGetTocQuery.ts @@ -37,6 +37,7 @@ async function fetchToc(url: string): Promise { return res.json() as Promise } +/** Fetches the TableOfContent data from URL */ export function useGetTocQuery(url: string) { const callback = useCallback(() => fetchToc(url), [url]) diff --git a/src/features/toc/core/buildMenuSection.ts b/src/features/toc/core/buildMenuSection.ts index 1c59cc9..5dce59b 100644 --- a/src/features/toc/core/buildMenuSection.ts +++ b/src/features/toc/core/buildMenuSection.ts @@ -1,24 +1,36 @@ import type { MenuItem, PageDescriptor, PageHighlight, PageId, PageURL, SectionHighlight, TableOfContent } from '../types' type BuildMenuBaseOptions = { + /** + * Current page URL to highlight in the menu + * If it is necessary to pass ID instead of URL, then we will need to replace this field with ID + */ url: PageURL + /** Current page ancestors */ breadcrumbs: PageDescriptor[] + /** Items that match the search filter and its ancestors */ filter?: Set | null } type BuildMenuTopLevelOptions = BuildMenuBaseOptions type BuildMenuNestingOptions = BuildMenuBaseOptions & { + /** ID of the page whose children we want to render */ parentId: string + /** The level of current menu items */ level: number + /** The relation of pages to the active page for proper highlighting */ highlight: SectionHighlight } type BuildMenuOptions = BuildMenuTopLevelOptions | BuildMenuNestingOptions type PageProps = { + /** The relation of the page to the active page for proper highlighting */ highlight: PageHighlight + /** Should the page be opened by default */ defaultOpenState: boolean + /** The level of the current page */ level: number } @@ -79,6 +91,10 @@ export const buildMenuSection = (toc: TableOfContent, options: BuildMenuOptions) let highlight: PageHighlight = sectionHighlight let defaultOpenState = false + /** + * If it is necessary to pass ID instead of URL, then we will need to replace this check with + * page.id === options.id + */ if (page.url === url) { highlight = 'active' defaultOpenState = Boolean(page.pages?.length) diff --git a/src/features/toc/core/filterTreeNodes.ts b/src/features/toc/core/filterTreeNodes.ts index 4febfaa..6a49a93 100644 --- a/src/features/toc/core/filterTreeNodes.ts +++ b/src/features/toc/core/filterTreeNodes.ts @@ -2,6 +2,14 @@ import type { PageDescriptor, TableOfContent } from '../types' import { isTextMatch } from './isTextMatch' +/** + * Filter TOC by the search string. + * The page is considering suitable, if it or one of its descendants matches the text + * + * @param toc The TOC to be filtered + * @param search The string to find in pages titles + * @returns Set of pages that should be presented in the search results + */ export const filterTreeNodes = (toc: TableOfContent, search: string): Set => { const filterResult = new Set() const normalizedSearch = search.toLocaleLowerCase() diff --git a/src/features/toc/core/getBreadCrumbs.ts b/src/features/toc/core/getBreadCrumbs.ts index 6e5645e..e4ff3af 100644 --- a/src/features/toc/core/getBreadCrumbs.ts +++ b/src/features/toc/core/getBreadCrumbs.ts @@ -1,6 +1,11 @@ import type { PageDescriptor, PageURL, PageId, TableOfContent } from '../types' import { getCurrentPage } from './getCurrentPage' +/** + * @param toc The TOC data + * @param url Page URL + * @returns Current page and the array of its ancestors from top to bottom + */ export const getBreadCrumbs = (toc: TableOfContent, url?: PageURL): PageDescriptor[] => { const breadcrumbs: PageDescriptor[] = [] diff --git a/src/features/toc/core/isTextMatch.ts b/src/features/toc/core/isTextMatch.ts index 80efa47..0370d91 100644 --- a/src/features/toc/core/isTextMatch.ts +++ b/src/features/toc/core/isTextMatch.ts @@ -1,3 +1,10 @@ +/** + * Rough fuzzy search that finds "omit" in "jOhn sMITh". + * + * @param value value to check, e.g. a page title "jOhn sMITh" + * @param search string to search in the value, e.g. "omit" + * @returns boolean if the value matches the search string + */ export const isTextMatch = (value: string, search: string) => { const normalizedValue = value.toLowerCase() diff --git a/src/features/toc/ui/Menu/Context/MenuProvider.tsx b/src/features/toc/ui/Menu/Context/MenuProvider.tsx index 04e322e..7998ee1 100644 --- a/src/features/toc/ui/Menu/Context/MenuProvider.tsx +++ b/src/features/toc/ui/Menu/Context/MenuProvider.tsx @@ -6,8 +6,11 @@ import type { PageURL, TableOfContent } from '../../../types' import { FilterContext, LocationContext, TocContext } from './contexts' type MenuProviderProps = PropsWithChildren<{ + /** The whole TOC tree */ toc: TableOfContent + /** Current URL */ url: PageURL + /** Is the TOC loading */ isLoading?: boolean }> diff --git a/src/features/toc/ui/Menu/Context/hooks.ts b/src/features/toc/ui/Menu/Context/hooks.ts index 9eb3d06..12fa04c 100644 --- a/src/features/toc/ui/Menu/Context/hooks.ts +++ b/src/features/toc/ui/Menu/Context/hooks.ts @@ -46,6 +46,10 @@ export const useFilterInput = () => { const { isFiltering, onChange, onFilterStart, onReset } = useContext(FilterContext) const timeout = useRef(0) + /** + * Filter results should appear when the user enters the whole query. + * So waiting for FILTER_DELAY_IN_MS until the user stops typing + */ const onChangeHandler = useCallback((value: string) => { if (timeout.current) { clearTimeout(timeout.current) diff --git a/src/features/toc/ui/Menu/Item/Item.tsx b/src/features/toc/ui/Menu/Item/Item.tsx index 6e8db55..b9a5f65 100644 --- a/src/features/toc/ui/Menu/Item/Item.tsx +++ b/src/features/toc/ui/Menu/Item/Item.tsx @@ -11,15 +11,22 @@ import { useIsLoading } from '../Context/hooks' import styles from './Item.module.css' type ItemProps = PropsWithChildren<{ + /** The item :) */ item: MenuItem + /** OnClick handler, e.g. expand/collapse the section */ onClick?: () => void + /** Show the component; triggers the enter or exit states for the animation */ isVisible?: boolean }> type ItemToggleProps = { + /** The item */ item: MenuItem + /** ReactChildren to show when item is opened */ children: (isOpen: boolean) => JSX.Element + /** Show the component; triggers the enter or exit states for the animation */ isVisible: boolean + /** Is it allowed to change the toggle state */ isDisabled?: boolean } @@ -88,6 +95,11 @@ export function ItemToggle({ item, children, isDisabled, isVisible }: ItemToggle } const hasUrl = Boolean(item.url) + /** + * While the menu is loading, it makes no sense to collapse and expand the items. So forbid them to collapse. + * Also, when using the search, it is not necessary to collapse the items, because the search result may be + * a leaf of the tree. So forbid collapsing items in search mode too. + */ const shouldBeForciblyOpened = isLoading || isDisabled const shouldShowChildren = shouldBeForciblyOpened || isOpen && isVisible const shouldPreventClose = shouldBeForciblyOpened || isOpen && hasUrl diff --git a/src/features/toc/ui/Menu/Menu.tsx b/src/features/toc/ui/Menu/Menu.tsx index 3b93603..0cd8672 100644 --- a/src/features/toc/ui/Menu/Menu.tsx +++ b/src/features/toc/ui/Menu/Menu.tsx @@ -7,8 +7,11 @@ import { List } from './List/List' import styles from './Menu.module.css' export type MenuProps = { + /** The whole TOC tree */ toc: TableOfContent + /** Current URL */ activeUrl: string + /** Is the TOC loading */ isLoading?: boolean } diff --git a/src/features/toc/ui/Menu/Section/Section.tsx b/src/features/toc/ui/Menu/Section/Section.tsx index a23fc61..293fb51 100644 --- a/src/features/toc/ui/Menu/Section/Section.tsx +++ b/src/features/toc/ui/Menu/Section/Section.tsx @@ -3,9 +3,13 @@ import { useSectionItems } from '../Context/hooks' import { Item, ItemToggle } from '../Item/Item' type SectionProps = { + /** ID of the page whose children we want to render */ parentId: PageId + /** The level of the current section */ level: number + /** The relation of the section to the active page for proper highlighting */ highlight?: SectionHighlight + /** Show the component; triggers the enter or exit states for the animation */ isVisible?: boolean } diff --git a/src/hooks/useCurrentPageUrl.ts b/src/hooks/useCurrentPageUrl.ts index e454f42..bad36c0 100644 --- a/src/hooks/useCurrentPageUrl.ts +++ b/src/hooks/useCurrentPageUrl.ts @@ -1,5 +1,9 @@ import { useLocation } from 'react-router-dom' +/** + * Extract current page url from the router, and correct it to the TOC format + * The TOC links has no leading slash, so remove it + */ export function useCurrentPageUrl() { const location = useLocation() const currentUrl = location.pathname.replace(/^\//, '') diff --git a/src/test/render.tsx b/src/test/render.tsx index 6f5dd31..858c69d 100644 --- a/src/test/render.tsx +++ b/src/test/render.tsx @@ -4,6 +4,7 @@ import { render, renderHook, type RenderOptions } from '@testing-library/react' import { TestRouter } from '../app/Router' type AppProvidersProps = { + /** Current URL */ url?: string }