diff --git a/plugins/techdocs-module-addons-contrib/api-report.md b/plugins/techdocs-module-addons-contrib/api-report.md index 4a93356365..b40d8fe76c 100644 --- a/plugins/techdocs-module-addons-contrib/api-report.md +++ b/plugins/techdocs-module-addons-contrib/api-report.md @@ -8,7 +8,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; // @public -export const ExpandableNavigation: (props: unknown) => JSX.Element | null; +export const ExpandableNavigation: () => JSX.Element | null; // @public export const ReportIssue: (props: ReportIssueProps) => JSX.Element | null; diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx index 486ddcd267..5ef76187e3 100644 --- a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useCallback, useState } from 'react'; import { useLocalStorageValue } from '@react-hookz/web'; import { Button, withStyles } from '@material-ui/core'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { useShadowRoot } from '@backstage/plugin-techdocs-react'; +import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; const NESTED_LIST_TOGGLE = '.md-nav__item--nested .md-toggle'; @@ -52,12 +52,15 @@ const ExpandedIcon = withStyles({ })(ExpandMoreIcon); type expandableNavigationLocalStorage = { - navExpanded: boolean; + expandAllNestedNavs: boolean; }; +/** + * Show expand/collapse navigation button next to site name in main + * navigation menu if documentation site has nested navigation. + */ export const ExpandableNavigationAddon = () => { - const shadowRoot = useShadowRoot(); - const defaultValue = { navExpanded: false }; + const defaultValue = { expandAllNestedNavs: false }; const [expanded, setExpanded] = useLocalStorageValue( EXPANDABLE_NAVIGATION_LOCAL_STORAGE, @@ -65,25 +68,43 @@ export const ExpandableNavigationAddon = () => { ); const [hasNavSubLevels, setHasNavSubLevels] = useState(false); + const [...checkboxToggles] = useShadowRootElements([ + NESTED_LIST_TOGGLE, + ]); + + const shouldToggle = useCallback( + (item: HTMLInputElement) => { + const isExpanded = item.checked; + const shouldExpand = expanded?.expandAllNestedNavs; + + // Is collapsed but should expand + if (shouldExpand && !isExpanded) { + return true; + } + + // Is expanded but should collapse + if (!shouldExpand && isExpanded) { + return true; + } + + return false; + }, + [expanded], + ); + useEffect(() => { - const checkboxToggles = - shadowRoot?.querySelectorAll(NESTED_LIST_TOGGLE); - if (!checkboxToggles || (checkboxToggles && checkboxToggles.length === 0)) - return; + // There is no nested navs + if (!checkboxToggles?.length) return; + setHasNavSubLevels(true); checkboxToggles.forEach(item => { - if ( - (expanded?.navExpanded && !item.checked) || - (!expanded?.navExpanded && item.checked) - ) { - item.click(); - } + if (shouldToggle(item)) item.click(); }); - }, [shadowRoot, expanded]); + }, [expanded, shouldToggle, checkboxToggles]); const handleState = () => { setExpanded(prevState => ({ - navExpanded: !prevState?.navExpanded, + expandAllNestedNavs: !prevState?.expandAllNestedNavs, })); }; @@ -93,9 +114,11 @@ export const ExpandableNavigationAddon = () => { - {expanded?.navExpanded ? : } + {expanded?.expandAllNestedNavs ? : } ) : null} diff --git a/plugins/techdocs-module-addons-contrib/src/plugin.ts b/plugins/techdocs-module-addons-contrib/src/plugin.ts index dbef119da8..06fa7db07d 100644 --- a/plugins/techdocs-module-addons-contrib/src/plugin.ts +++ b/plugins/techdocs-module-addons-contrib/src/plugin.ts @@ -34,7 +34,39 @@ export const techdocsModuleAddonsContribPlugin = createPlugin({ }); /** - * TechDocs addon that lets you expand/collapse the TechDocs main navigation. + * TechDocs addon that lets you expand/collapse the TechDocs main navigation + * and keep the preferred state in local storage. The addon will render as + * a button next to the site name if the documentation has nested navigation. + * + * @example + * Here's a simple example: + * ``` + * import { + * DefaultTechDocsHome, + * TechDocsIndexPage, + * TechDocsReaderPage, + * } from '@backstage/plugin-techdocs'; + * import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha'; + * import { ExpandableNavigation } from '@backstage/plugin-techdocs-module-addons-contrib'; + * + * + * const AppRoutes = () => { + * + * // other plugin routes + * }> + * + * + * } + * > + * + * + * + * + * ; + * }; + * ``` * * @public */ diff --git a/plugins/techdocs-react/src/addons.tsx b/plugins/techdocs-react/src/addons.tsx index 49aa5b1bcc..7629c94858 100644 --- a/plugins/techdocs-react/src/addons.tsx +++ b/plugins/techdocs-react/src/addons.tsx @@ -51,6 +51,14 @@ const getDataKeyByName = (name: string) => { * Create a TechDocs addon. * @public */ +export function createTechDocsAddonExtension( + options: TechDocsAddonOptions, +): Extension<() => JSX.Element | null>; + +export function createTechDocsAddonExtension( + options: TechDocsAddonOptions, +): Extension<(props: TComponentProps) => JSX.Element | null>; + export function createTechDocsAddonExtension( options: TechDocsAddonOptions, ): Extension<(props: TComponentProps) => JSX.Element | null> {