Skip to content

Commit

Permalink
chore(docs): start adding layout docs and example projects
Browse files Browse the repository at this point in the history
  • Loading branch information
mlaursen committed Apr 20, 2024
1 parent 7fedee1 commit c8aa1b2
Show file tree
Hide file tree
Showing 31 changed files with 1,255 additions and 46 deletions.
14 changes: 14 additions & 0 deletions apps/docs/src/app/(layout-examples)/RootProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";
import { rmdConfig } from "@/constants/rmdConfig.jsx";
import { CoreProviders } from "@react-md/core/CoreProviders";
import { type ReactElement, type ReactNode } from "react";

export interface RootProvidersProps {
children: ReactNode;
}

export function RootProviders(props: RootProvidersProps): ReactElement {
const { children } = props;

return <CoreProviders {...rmdConfig}>{children}</CoreProviders>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client";
import { AppBar } from "@react-md/core/app-bar/AppBar";
import { AppBarTitle } from "@react-md/core/app-bar/AppBarTitle";
import { Button } from "@react-md/core/button/Button";
import { LayoutNav } from "@react-md/core/layout/LayoutNav";
import { Main } from "@react-md/core/layout/Main";
import {
useExpandableLayout,
type ExpandableLayoutOptions,
} from "@react-md/core/layout/useExpandableLayout";
import { Sheet } from "@react-md/core/sheet/Sheet";
import { usePathname } from "next/navigation.js";
import { type ReactElement } from "react";
import { Navigation } from "./Navigation.jsx";
import { type ExampleLayoutProps } from "./layouts.js";

export interface ExpandableLayoutExampleProps
extends ExampleLayoutProps,
Omit<ExpandableLayoutOptions, "pathname"> {}

export function ExpandableLayoutExample(
props: ExpandableLayoutExampleProps
): ReactElement {
const { layout, children, ...options } = props;

const pathname = usePathname();
const {
temporary,
temporaryNavProps,
navToggleProps,
appBarProps,
mainProps,
expandableNavProps,
} = useExpandableLayout({ pathname, ...options });

return (
<>
<AppBar {...appBarProps}>
<Button {...navToggleProps} />
<AppBarTitle>Expandable Layout Example</AppBarTitle>
</AppBar>
<LayoutNav {...expandableNavProps}>
<Navigation layout={layout} />
</LayoutNav>
{temporary && (
<Sheet {...temporaryNavProps}>
<Navigation layout={layout} />
</Sheet>
)}
<Main {...mainProps}>{children}</Main>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type ReactElement } from "react";
import { type ExampleLayoutProps } from "./layouts.js";
import { ExpandableLayoutExample } from "./ExpandableLayoutExample.js";

export function FullHeightExpandableLayoutExample(
props: ExampleLayoutProps
): ReactElement {
return <ExpandableLayoutExample {...props} fullHeightNav defaultExpanded />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type ReactElement } from "react";
import { type ExampleLayoutProps } from "./layouts.js";
import { ExpandableLayoutExample } from "./ExpandableLayoutExample.jsx";

export function FullHeightLayoutExample(
props: ExampleLayoutProps
): ReactElement {
return <ExpandableLayoutExample {...props} fullHeightNav="static" />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type ReactElement } from "react";
import { type ExampleLayoutProps } from "./layouts.js";
import { ResizableLayoutExample } from "./ResizableLayoutExample.jsx";

export function FullHeightResizableLayoutExample(
props: ExampleLayoutProps
): ReactElement {
return <ResizableLayoutExample {...props} fullHeightNav="static" />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ForestIcon from "@react-md/material-icons/ForestIcon";
import AllOutIcon from "node_modules/@react-md/material-icons/src/AllOutIcon.jsx";
import DensityLargeIcon from "node_modules/@react-md/material-icons/src/DensityLargeIcon.jsx";
import ExpandIcon from "node_modules/@react-md/material-icons/src/ExpandIcon.jsx";
import FullscreenIcon from "node_modules/@react-md/material-icons/src/FullscreenIcon.jsx";
import MenuOpenIcon from "node_modules/@react-md/material-icons/src/MenuOpenIcon.jsx";
import OpenInFullIcon from "node_modules/@react-md/material-icons/src/OpenInFullIcon.jsx";
import { type ComponentType, type ReactElement } from "react";
import { type LayoutType } from "./layouts.js";
import { type SVGIconProps } from "@react-md/core/icon/SVGIcon";

export interface LayoutIconProps extends SVGIconProps {
layout: LayoutType;
}

export function LayoutIcon(props: LayoutIconProps): ReactElement {
const { layout, ...remaining } = props;
let Component: ComponentType<SVGIconProps>;
switch (layout) {
case "temporary":
Component = MenuOpenIcon;
break;
case "expandable":
Component = ExpandIcon;
break;
case "resizable":
Component = OpenInFullIcon;
break;
case "full-height":
Component = FullscreenIcon;
break;
case "full-height-expandable":
Component = DensityLargeIcon;
break;
case "full-height-resizable":
Component = AllOutIcon;
break;
case "tree":
Component = ForestIcon;
break;
}

return <Component {...remaining} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { pascalCase } from "@/utils/strings.js";
import { cssUtils } from "@react-md/core/cssUtils";
import { Divider } from "@react-md/core/divider/Divider";
import { List } from "@react-md/core/list/List";
import { ListSubheader } from "@react-md/core/list/ListSubheader";
import HomeIcon from "@react-md/material-icons/HomeIcon";
import StarIcon from "@react-md/material-icons/StarIcon";
import ExitToAppIcon from "node_modules/@react-md/material-icons/src/ExitToAppIcon.jsx";
import FavoriteIcon from "node_modules/@react-md/material-icons/src/FavoriteIcon.jsx";
import { type ReactElement } from "react";
import { LayoutIcon } from "./LayoutIcon.jsx";
import { SimpleNavItem } from "./SimpleNavItem.jsx";
import { LAYOUT_TYPES, type LayoutType } from "./layouts.js";

export interface NavigationProps {
layout: LayoutType;
}

export function Navigation(props: NavigationProps): ReactElement {
const { layout } = props;

return (
<List className={cssUtils({ textOverflow: "ellipsis" })}>
<SimpleNavItem
href={`/layout-example/${layout}`}
leftAddon={<HomeIcon />}
>
Home
</SimpleNavItem>
<SimpleNavItem
href={`/layout-example/${layout}/page-1`}
leftAddon={<StarIcon />}
>
Page 1
</SimpleNavItem>
<SimpleNavItem
href={`/layout-example/${layout}/page-2`}
leftAddon={<FavoriteIcon />}
>
Page 2
</SimpleNavItem>
<Divider />
<ListSubheader>Layout Types</ListSubheader>
{LAYOUT_TYPES.map((layoutType) => (
<SimpleNavItem
key={layoutType}
active={layout === layoutType}
href={`/layout-example/${layoutType}`}
leftAddon={<LayoutIcon layout={layoutType} />}
>
{pascalCase(layoutType, " ")}
</SimpleNavItem>
))}
<Divider />
<SimpleNavItem
href={`/getting-started/layout#${layout}-navigation-layout`}
leftAddon={<ExitToAppIcon />}
>
Back to layout docs
</SimpleNavItem>
</List>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"use client";
import { ButtonStyledLink } from "@/components/ButtonStyledLink.jsx";
import { LinkUnstyled } from "@/components/LinkUnstyled.jsx";
import { pascalCase } from "@/utils/strings.js";
import { box } from "@react-md/core/box/styles";
import { useLayoutTree } from "@react-md/core/layout/useLayoutTree";
import { Tree } from "@react-md/core/tree/Tree";
import { type TreeData, type TreeItemSorter } from "@react-md/core/tree/types";
import ExitToAppIcon from "@react-md/material-icons/ExitToAppIcon";
import HomeIcon from "@react-md/material-icons/HomeIcon";
import { cnb } from "cnbuilder";
import { usePathname } from "next/navigation.js";
import FavoriteIcon from "node_modules/@react-md/material-icons/src/FavoriteIcon.jsx";
import StarIcon from "node_modules/@react-md/material-icons/src/StarIcon.jsx";
import { useMemo, type ReactElement } from "react";
import { LayoutIcon } from "./LayoutIcon.jsx";
import { LAYOUT_TYPES, type LayoutType } from "./layouts.js";
import { cssUtils } from "@react-md/core/cssUtils";

const PATH_PREFIX = "/layout-example/";
const TYPE_SUFFIX = "-type";

const sort: TreeItemSorter = (items) => {
if (items.length !== LAYOUT_TYPES.length) {
return items;
}
const sorted = [...items];
sorted.sort((a, b) => {
const aType = a.itemId.substring(
PATH_PREFIX.length,
a.itemId.length - TYPE_SUFFIX.length
);
const bType = b.itemId.substring(
PATH_PREFIX.length,
b.itemId.length - TYPE_SUFFIX.length
);

return (
LAYOUT_TYPES.indexOf(bType as LayoutType) -
LAYOUT_TYPES.indexOf(aType as LayoutType)
);
});
sorted.reverse();

return sorted;
};

export interface NavigationTreeProps {
layout: LayoutType;
}

export function NavigationTree(props: NavigationTreeProps): ReactElement {
const { layout } = props;

const pathname = usePathname();
const navItems = useMemo<TreeData>(() => {
const homeHref = `${PATH_PREFIX}${layout}`;
const layouts = LAYOUT_TYPES.reduce<TreeData>((result, layoutType) => {
const href = `${PATH_PREFIX}${layoutType}`;
result[`${href}${TYPE_SUFFIX}`] = {
href,
itemId: `${href}${TYPE_SUFFIX}`,
children: pascalCase(layoutType, " "),
parentId: "layouts",
leftAddon: <LayoutIcon layout={layoutType} />,
contentClassName: cnb(
layout === layoutType && "rmd-tree-item__content--selected"
),
};

return result;
}, {});

return {
pages: {
itemId: "pages",
parentId: null,
children: "Pages",
},
layouts: {
itemId: "layouts",
parentId: null,
children: "Layout Types",
},
[homeHref]: {
href: homeHref,
itemId: homeHref,
parentId: "pages",
children: "Home",
leftAddon: <HomeIcon />,
},
[`${homeHref}/page-1`]: {
href: `${homeHref}/page-1`,
itemId: `${homeHref}/page-1`,
parentId: "pages",
children: "Page 1",
leftAddon: <StarIcon />,
},
[`${homeHref}/page-2`]: {
href: `${homeHref}/page-2`,
itemId: `${homeHref}/page-2`,
parentId: "pages",
children: "Page 2",
leftAddon: <FavoriteIcon />,
},
...layouts,
};
}, [layout]);
const tree = useLayoutTree({
navItems,
pathname,
defaultExpandedIds: ["layouts", "pages"],
});

return (
<>
<Tree
aria-label="Navigation"
{...tree}
sort={sort}
linkComponent={LinkUnstyled}
/>
<footer
className={box({
justify: "stretch",
fullWidth: true,
disableWrap: true,
className: cssUtils({ textOverflow: "ellipsis" }),
})}
>
<ButtonStyledLink
style={{ flex: "1 1 auto", justifyContent: "flex-start" }}
href={`/getting-started/layout#${layout}-navigation-layout`}
theme="secondary"
themeType="contained"
>
<ExitToAppIcon />
<span className={cssUtils({ textOverflow: "ellipsis" })}>
Back to layout docs
</span>
</ButtonStyledLink>
</footer>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.navigation {
grid-template-rows: 1fr min-content;
}
Loading

0 comments on commit c8aa1b2

Please sign in to comment.