Skip to content

Commit

Permalink
feat(navigation): support controlling expansion and following pathname
Browse files Browse the repository at this point in the history
  • Loading branch information
mlaursen committed Aug 31, 2024
1 parent 907306f commit 56f2b9f
Show file tree
Hide file tree
Showing 16 changed files with 399 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LinkUnstyled } from "@/components/LinkUnstyled.jsx";
import { pascalCase } from "@/utils/strings.js";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import ExitToAppIcon from "@react-md/material-icons/ExitToAppIcon";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import HomeIcon from "@react-md/material-icons/HomeIcon";
Expand Down Expand Up @@ -88,14 +89,10 @@ export function ExampleCoreNavigation(
],
[layout, pathname]
);
const { data } = useNavigationExpansion({
pathname: `${pathname}${navTypeParam}`,
linkComponent: LinkUnstyled,
});

return (
<Navigation
items={items}
data={{
pathname: `${pathname}${navTypeParam}`,
linkComponent: LinkUnstyled,
}}
/>
);
return <Navigation items={items} data={data} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FakeLink } from "@/components/FakeLink.jsx";
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import { type ReactElement } from "react";

const items: readonly NavigationItem[] = [
Expand Down Expand Up @@ -35,12 +36,13 @@ const items: readonly NavigationItem[] = [
];

export default function AddingDividersAndSubheadersExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation
data={{ pathname: "/", linkComponent: FakeLink }}
items={items}
/>
<Navigation data={data} items={items} />
</nav>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FakeLink } from "@/components/FakeLink.jsx";
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import HomeIcon from "@react-md/material-icons/HomeIcon";
import StarIcon from "@react-md/material-icons/StarIcon";
Expand Down Expand Up @@ -30,12 +31,13 @@ const items: readonly NavigationItem[] = [
];

export default function AddingIconsExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation
data={{ pathname: "/", linkComponent: FakeLink }}
items={items}
/>
<Navigation data={data} items={items} />
</nav>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FakeLink } from "@/components/FakeLink.jsx";
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import { type ReactElement } from "react";

const items: readonly NavigationItem[] = [
Expand Down Expand Up @@ -80,12 +81,13 @@ const items: readonly NavigationItem[] = [
];

export default function CollapsibleGroupsExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/route-1/page-2",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation
data={{ pathname: "/route-1/page-2", linkComponent: FakeLink }}
items={items}
/>
<Navigation data={data} items={items} />
</nav>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { LinkUnstyled } from "@/components/LinkUnstyled.jsx";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import { usePathname } from "next/navigation.js";
import { type ReactElement } from "react";

Expand All @@ -20,12 +21,13 @@ const items: readonly NavigationItem[] = [

export function MainNavigation(): ReactElement {
const pathname = usePathname();
const { data } = useNavigationExpansion({
pathname,
linkComponent: LinkUnstyled,
});
return (
<nav aria-label="Navigation">
<Navigation
data={{ pathname, linkComponent: LinkUnstyled }}
items={items}
/>
<Navigation data={data} items={items} />
</nav>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FakeLink } from "@/components/FakeLink.jsx";
import { card } from "@react-md/core/card/styles";
import { Navigation } from "@react-md/core/navigation/Navigation";
import { type NavigationItem } from "@react-md/core/navigation/types";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import { type ReactElement } from "react";

const items: readonly NavigationItem[] = [
Expand All @@ -23,12 +24,13 @@ const items: readonly NavigationItem[] = [
];

export default function SimpleExample(): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: FakeLink,
});
return (
<nav aria-label="Fake Navigation" className={card()}>
<Navigation
data={{ pathname: "/", linkComponent: FakeLink }}
items={items}
/>
<Navigation data={data} items={items} />
</nav>
);
}
13 changes: 6 additions & 7 deletions apps/docs/src/components/MainLayout/MainNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Navigation } from "@react-md/core/navigation/Navigation";
import { useNavigationExpansion } from "@react-md/core/navigation/useNavigationExpansion";
import { usePathname } from "next/navigation.js";
import { type ReactElement } from "react";
import { LinkUnstyled } from "../LinkUnstyled.jsx";
Expand All @@ -11,12 +12,10 @@ export interface MainNavigationProps {
export function MainNavigation(props: MainNavigationProps): ReactElement {
const { className } = props;
const pathname = usePathname();
const { data } = useNavigationExpansion({
pathname,
linkComponent: LinkUnstyled,
});

return (
<Navigation
data={{ pathname, linkComponent: LinkUnstyled }}
items={navItems}
className={className}
/>
);
return <Navigation data={data} items={navItems} className={className} />;
}
14 changes: 13 additions & 1 deletion packages/codemod/transforms/v5-to-v6/coreExportMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,9 @@ export const TYPES: ReadonlySet<string> = new Set([
"NativeSelectProps",
"NavGroupClassNameOptions",
"NavGroupProps",
"NavigationExpansion",
"NavigationExpansionImplementation",
"NavigationExpansionOptions",
"NavigationItem",
"NavigationItemDivider",
"NavigationItemGroup",
Expand Down Expand Up @@ -1232,6 +1235,7 @@ export const VARIABLES: ReadonlySet<string> = new Set([
"getNextFocusableIndex",
"getNonDisabledOptions",
"getOrientationType",
"getPartsFromPathname",
"getPercentage",
"getPosition",
"getProgressA11y",
Expand Down Expand Up @@ -1746,6 +1750,7 @@ export const VARIABLES: ReadonlySet<string> = new Set([
"useMenuConfiguration",
"useMenuVisibility",
"useMutationObserver",
"useNavigationExpansion",
"useNestedDialogContext",
"useNumberField",
"useOrientation",
Expand Down Expand Up @@ -2506,7 +2511,7 @@ export const EXPORT_MAP: Record<string, string> = {
getFocusableElements: "@react-md/core/focus/utils",
getFontVariationSettings: "@react-md/core/icon/materialConfig",
getFormConfig: "@react-md/core/form/formConfig",
getHrefFromParents: "@react-md/core/navigation/getHrefFromParents",
getHrefFromParents: "@react-md/core/navigation/utils",
getIcon: "@react-md/core/icon/iconConfig",
getInnerLeftCoord: "@react-md/core/positioning/utils",
getInnerRightCoord: "@react-md/core/positioning/utils",
Expand All @@ -2523,6 +2528,7 @@ export const EXPORT_MAP: Record<string, string> = {
getNextFocusableIndex: "@react-md/core/movement/utils",
getNonDisabledOptions: "@react-md/core/form/useCombobox",
getOrientationType: "@react-md/core/useOrientation",
getPartsFromPathname: "@react-md/core/navigation/utils",
GetPasswordVisibilityIcon: "@react-md/core/form/Password",
getPercentage: "@react-md/core/utils/getPercentage",
GetPercentageOptions: "@react-md/core/utils/getPercentage",
Expand Down Expand Up @@ -2913,6 +2919,11 @@ export const EXPORT_MAP: Record<string, string> = {
NavGroupClassNameOptions: "@react-md/core/navigation/navGroupStyles",
NavGroupProps: "@react-md/core/navigation/NavGroup",
Navigation: "@react-md/core/navigation/Navigation",
NavigationExpansion: "@react-md/core/navigation/types",
NavigationExpansionImplementation:
"@react-md/core/navigation/useNavigationExpansion",
NavigationExpansionOptions:
"@react-md/core/navigation/useNavigationExpansion",
NavigationItem: "@react-md/core/navigation/types",
NavigationItemDivider: "@react-md/core/navigation/types",
NavigationItemGroup: "@react-md/core/navigation/types",
Expand Down Expand Up @@ -3627,6 +3638,7 @@ export const EXPORT_MAP: Record<string, string> = {
useMenuConfiguration: "@react-md/core/menu/MenuConfigurationProvider",
useMenuVisibility: "@react-md/core/menu/MenuVisibilityProvider",
useMutationObserver: "@react-md/core/useMutationObserver",
useNavigationExpansion: "@react-md/core/navigation/useNavigationExpansion",
useNestedDialogContext: "@react-md/core/dialog/NestedDialogProvider",
useNumberField: "@react-md/core/form/useNumberField",
useOrientation: "@react-md/core/useOrientation",
Expand Down
13 changes: 6 additions & 7 deletions packages/core/src/navigation/CollapsibleNavGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import { forwardRef, type LiHTMLAttributes, type ReactNode } from "react";
import { type ButtonProps } from "../button/Button.js";
import { useCollapseTransition } from "../transition/useCollapseTransition.js";
import { type PropsWithRef, type UseStateInitializer } from "../types.js";
import { useToggle } from "../useToggle.js";
import { type PropsWithRef } from "../types.js";
import { NavGroup, type NavGroupProps } from "./NavGroup.js";
import { NavItem } from "./NavItem.js";
import {
Expand Down Expand Up @@ -35,8 +34,8 @@ export interface CollapsibleNavGroupProps
*/
children: ReactNode;

/** @defaultValue `false` */
defaultCollapsed?: UseStateInitializer<boolean>;
collapsed: boolean;
toggleCollapsed(): void;
}

/**
Expand All @@ -57,11 +56,11 @@ export const CollapsibleNavGroup = forwardRef<
disableIconRotator,
className,
children,
defaultCollapsed,
collapsed,
toggleCollapsed,
...remaining
} = props;

const { toggled: collapsed, toggle } = useToggle(defaultCollapsed);
const { rendered, elementProps } = useCollapseTransition({
nodeRef: ref,
className,
Expand All @@ -75,7 +74,7 @@ export const CollapsibleNavGroup = forwardRef<
{...buttonProps}
onClick={(event) => {
onButtonClick(event);
toggle();
toggleCollapsed();
}}
collapsed={collapsed}
icon={icon}
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/navigation/DefaultNavigationRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type RenderRecursiveItemsProps } from "../utils/RenderRecursively.js";
import { CollapsibleNavGroup } from "./CollapsibleNavGroup.js";
import { NavItemLink } from "./NavItemLink.js";
import { NavSubheader } from "./NavSubheader.js";
import { getHrefFromParents } from "./getHrefFromParents.js";
import { getHrefFromParents } from "./utils.js";
import { type NavigationItem, type NavigationRenderData } from "./types.js";

/**
Expand Down Expand Up @@ -46,13 +46,15 @@ export function DefaultNavigationRenderer<

if (item.items) {
const nextParents = [item, ...parents];
const href = getHrefFromParents(nextParents);
return (
<CollapsibleNavGroup
depth={nextParents.length}
defaultCollapsed={() =>
!data?.pathname.includes(getHrefFromParents(nextParents))
}
collapsed={!data?.expandedItems.has(href)}
buttonChildren={item.children}
toggleCollapsed={() => {
data?.toggleExpandedItem(href);
}}
>
{children}
</CollapsibleNavGroup>
Expand Down
29 changes: 21 additions & 8 deletions packages/core/src/navigation/__tests__/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { describe, expect, it } from "@jest/globals";
import { FontIcon } from "../../icon/FontIcon.js";
import { render, screen, userEvent, within } from "../../test-utils/index.js";
import {
render as baseRender,
screen,
userEvent,
within,
} from "../../test-utils/index.js";
import { Navigation, type NavigationProps } from "../Navigation.js";
import { type NavigationItem } from "../types.js";
import { type ReactElement } from "react";
import { useNavigationExpansion } from "../useNavigationExpansion.js";

const items: NavigationItem[] = [
{
Expand Down Expand Up @@ -57,15 +64,21 @@ const items: NavigationItem[] = [
},
];

const BASE_PROPS: NavigationProps = {
"data-testid": "nav",
data: { pathname: "/", linkComponent: "a" },
items,
};
function Test(props: Partial<NavigationProps>): ReactElement {
const { data } = useNavigationExpansion({
pathname: "/",
linkComponent: "a",
});

return <Navigation data-testid="nav" data={data} items={items} {...props} />;
}

const render = (props?: Partial<NavigationProps>) =>
baseRender(<Test {...props} />);

describe("Navigation", () => {
it("should be able to render dividers, subheaders, groups, links, and nested items out of the box", () => {
render(<Navigation {...BASE_PROPS} />);
render();

const nav = screen.getByTestId("nav");
expect(nav).toMatchSnapshot();
Expand All @@ -80,7 +93,7 @@ describe("Navigation", () => {

it("should allow route groups to be expanded", async () => {
const user = userEvent.setup();
render(<Navigation {...BASE_PROPS} />);
render();
const path2 = screen.getByRole("button", { name: "Path 2" });
const path2Parent = path2.parentElement;
if (!path2Parent) {
Expand Down
Loading

0 comments on commit 56f2b9f

Please sign in to comment.