diff --git a/.gitignore b/.gitignore
index 98b48ed1..73b9c2e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,8 @@ storybook-static
# local test
mui-treasury.config.js
-mui-treasury
+/mui-treasury
+/src/mui-treasury
build-storybook.log
chromatic.log
diff --git a/examples/mui-treasury-layout-nextjs/.gitignore b/examples/mui-treasury-layout-nextjs/.gitignore
new file mode 100644
index 00000000..fd3dbb57
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/examples/mui-treasury-layout-nextjs/README.md b/examples/mui-treasury-layout-nextjs/README.md
new file mode 100644
index 00000000..2b3ef7b4
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/README.md
@@ -0,0 +1,45 @@
+# MUI Treasury Layout - Next.js App Router example in TypeScript
+
+This is a [Next.js](https://nextjs.org/) project bootstrapped using [`create-next-app`](https://github.com/vercel/next.js/tree/HEAD/packages/create-next-app) with MUI Treasury Layout ([standard preset](https://mui-treasury.com/?path=/story/layout-v6-preset-standard--standard)) installed.
+
+## How to use
+
+Download the example:
+
+
+
+```bash
+curl https://codeload.github.com/siriwatknp/mui-treasury/tar.gz/next | tar -xz --strip=2 mui-treasury-master/examples/mui-treasury-layout-nextjs
+cd mui-treasury-layout-nextjs
+```
+
+Install it and run:
+
+```bash
+yarn install
+yarn dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+or:
+
+
+
+[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/siriwatknp/mui-treasury/tree/master/examples/mui-treasury-layout-nextjs)
+
+[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/github/siriwatknp/mui-treasury/tree/master/examples/mui-treasury-layout-nextjs)
+
+## Learn more
+
+To learn more about this example:
+
+- [Next.js documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [MUI Treasury Layout](https://mui-treasury.com/?path=/docs/layout-v6-introduction--docs) - learn about layout configuration.
+
+## What's next?
+
+
+
+You now have a working example project.
+You can head back to the documentation and continue by browsing the [templates](https://next.mui.com/material-ui/getting-started/templates/) section.
diff --git a/examples/mui-treasury-layout-nextjs/next.config.mjs b/examples/mui-treasury-layout-nextjs/next.config.mjs
new file mode 100644
index 00000000..4678774e
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/examples/mui-treasury-layout-nextjs/package.json b/examples/mui-treasury-layout-nextjs/package.json
new file mode 100644
index 00000000..1815c576
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "mui-treasury-layout-nextjs",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@emotion/react": "latest",
+ "@emotion/styled": "latest",
+ "@mui/icons-material": "next",
+ "@mui/material": "next",
+ "@mui/material-nextjs": "next",
+ "next": "latest",
+ "react": "latest",
+ "react-dom": "latest"
+ },
+ "devDependencies": {
+ "@types/node": "latest",
+ "@types/react": "latest",
+ "@types/react-dom": "latest",
+ "typescript": "latest"
+ }
+}
diff --git a/examples/mui-treasury-layout-nextjs/public/next.svg b/examples/mui-treasury-layout-nextjs/public/next.svg
new file mode 100644
index 00000000..5174b28c
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/mui-treasury-layout-nextjs/src/app/Provider.tsx b/examples/mui-treasury-layout-nextjs/src/app/Provider.tsx
new file mode 100644
index 00000000..7c191558
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/app/Provider.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import React from "react";
+import CssBaseline from "@mui/material/CssBaseline";
+import { createTheme, ThemeProvider } from "@mui/material/styles";
+import type {} from "@mui/material/themeCssVarsAugmentation";
+
+const theme = createTheme({
+ cssVariables: true,
+ colorSchemes: { light: true, dark: true },
+ typography: {
+ fontFamily: "var(--font-inter)",
+ },
+});
+
+export default function Provider({ children }: { children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/examples/mui-treasury-layout-nextjs/src/app/favicon.ico b/examples/mui-treasury-layout-nextjs/src/app/favicon.ico
new file mode 100644
index 00000000..718d6fea
Binary files /dev/null and b/examples/mui-treasury-layout-nextjs/src/app/favicon.ico differ
diff --git a/examples/mui-treasury-layout-nextjs/src/app/layout.tsx b/examples/mui-treasury-layout-nextjs/src/app/layout.tsx
new file mode 100644
index 00000000..37246c70
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/app/layout.tsx
@@ -0,0 +1,21 @@
+import type { Metadata } from "next";
+import Provider from "./Provider";
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/examples/mui-treasury-layout-nextjs/src/app/page.tsx b/examples/mui-treasury-layout-nextjs/src/app/page.tsx
new file mode 100644
index 00000000..b0132da0
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/app/page.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import React from "react";
+import {
+ applyEdgeSidebarStyles,
+ applyHeaderStyles,
+ Content,
+ EdgeSidebar,
+ EdgeSidebarContent,
+ EdgeTemporaryClose,
+ Footer,
+ Header,
+ layoutClasses,
+ Root,
+ toggleEdgeSidebarCollapse,
+ toggleTemporaryEdgeSidebar,
+} from "@/mui-treasury/layout-core-v6";
+import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
+import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
+import Menu from "@mui/icons-material/Menu";
+import ButtonBase from "@mui/material/ButtonBase";
+import Container from "@mui/material/Container";
+import IconButton from "@mui/material/IconButton";
+import Image from "next/image";
+
+export default function Home() {
+ return (
+
+
+ {
+ toggleTemporaryEdgeSidebar();
+ }}
+ sx={{ mr: 1 }}
+ >
+
+
+
+
+ ({
+ ...applyEdgeSidebarStyles({
+ theme,
+ config: {
+ xs: {
+ variant: "temporary",
+ width: "256px",
+ },
+ sm: {
+ variant: "permanent",
+ width: "256px",
+ autoCollapse: "sm",
+ collapsedWidth: "64px",
+ },
+ },
+ }),
+ })}
+ >
+
+
+ toggleEdgeSidebarCollapse({ event })}
+ sx={{ height: 48, mt: "auto" }}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Content.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Content.tsx
new file mode 100644
index 00000000..27ced61c
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Content.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+/**
+ * Note: StyledContent cannot have `overflow: auto` by default because
+ * it will break the InsetSidebar absolute positioning.
+ */
+const StyledContent = styled("main")({
+ gridArea: layoutClasses.Content,
+ minHeight: 0,
+ marginTop: "var(--Content-insetTop)",
+ marginBottom: "var(--Content-insetBottom)",
+});
+
+const Content = React.forwardRef(function Content(
+ { className, ...props },
+ ref,
+) {
+ return (
+
+ );
+});
+
+export default Content;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebar.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebar.tsx
new file mode 100644
index 00000000..84ce9c89
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebar.tsx
@@ -0,0 +1,305 @@
+import React from "react";
+import type {
+ PermanentConfig,
+ PersistentConfig,
+ TemporaryConfig,
+} from "./SharedEdgeSidebar";
+import { BoxProps } from "@mui/material/Box";
+import { Breakpoint, Theme } from "@mui/material/styles";
+import { layoutAttrs, layoutClasses } from "./layoutClasses";
+import {
+ EdgeSidebarRoot,
+ internalCollapseSidebar,
+ internalToggleSidebar,
+} from "./SharedEdgeSidebar";
+import { styled } from "./zero-styled";
+
+function applyTemporaryStyles(params: Omit) {
+ const { width = "300px", fullHeight } = params || {};
+ return {
+ "--EdgeSidebar-temporaryWidth": "0px",
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--EdgeSidebar-variant": "var(--temporary)",
+ [`.${layoutClasses.EdgeSidebarCollapser}`]: {
+ display: "none",
+ },
+ },
+ [`&[${layoutAttrs.isTemporaryEdgeSidebarOpen}], &[${layoutAttrs.isTemporaryEdgeSidebarClosing}]`]:
+ {
+ "--EdgeSidebar-temporaryWidth": width,
+ },
+ ...(fullHeight
+ ? { zIndex: 5 }
+ : { "--SidebarContent-offset": "var(--Header-height)" }),
+ };
+}
+
+function applyPersistentStyles(params: Omit) {
+ const { width = "256px", persistentBehavior = "fit" } = params || {};
+ return {
+ ...(persistentBehavior === "none" && {
+ zIndex: 2,
+ "--SidebarContent-width": `var(--collapsed, var(--_permanentWidth, 0px)) var(--uncollapsed, ${width})`,
+ "--SidebarContent-shadow":
+ "0 0 10px rgba(0,0,0,0.1), var(--EdgeSidebar-sidelineWidth) 0 var(--EdgeSidebar-sidelineColor)",
+ [`&:not([${layoutAttrs.isEdgeSidebarUncollapsed}])`]: {
+ "--SidebarContent-shadow": "none",
+ },
+ }),
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--EdgeSidebar-variant": "var(--permanent)",
+ "--EdgeSidebar-collapsedWidth": "0px",
+ ...(persistentBehavior === "none"
+ ? {
+ "--EdgeSidebar-permanentWidth": "0px",
+ }
+ : {
+ ...(width && {
+ "--EdgeSidebar-permanentWidth": width,
+ }),
+ }),
+ "--EdgeSidebar-collapsible": "var(--collapsed)",
+ [`.${layoutClasses.EdgeSidebarCollapser}`]: {
+ display: "var(--display, inline-flex)",
+ },
+ [`.${layoutClasses.TemporaryEdgeSidebarTrigger}`]: {
+ display: "none",
+ },
+ },
+ [`.${layoutClasses.Root}:has(&[${layoutAttrs.isEdgeSidebarUncollapsed}])`]:
+ {
+ "--EdgeSidebar-collapsible": "var(--uncollapsed)",
+ },
+ };
+}
+
+function applyPermanentStyles(params: Omit) {
+ if ("autoCollapse" in params && !params.collapsedWidth) {
+ console.warn(
+ "MUI Treasury Layout: `collapsedWidth` is required when `autoCollapse` is enabled.",
+ );
+ }
+ const { width, collapsedWidth } = params || {};
+ const defaultExpandConfig = {
+ delay: "0.3s",
+ shadow: "0 0 10px rgba(0,0,0,0.1)",
+ };
+ let expandConfig: undefined | typeof defaultExpandConfig;
+ if ("expandOnHover" in params) {
+ if (params.expandOnHover === true) {
+ expandConfig = defaultExpandConfig;
+ } else {
+ expandConfig = params.expandOnHover as typeof defaultExpandConfig;
+ }
+ }
+ return {
+ "--SidebarContent-shadow": "none",
+ "--SidebarContent-width": "var(--_permanentWidth, 0px)",
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--EdgeSidebar-variant": "var(--permanent)",
+ ...(width && {
+ "--EdgeSidebar-permanentWidth": width,
+ }),
+ ...(collapsedWidth && {
+ "--EdgeSidebar-collapsedWidth": collapsedWidth,
+ }),
+ [`.${layoutClasses.EdgeSidebarCollapser}`]: {
+ display: "var(--display, inline-flex)",
+ },
+ [`.${layoutClasses.TemporaryEdgeSidebarTrigger}`]: {
+ display: "none",
+ },
+ },
+ ...(expandConfig && {
+ [`& .${layoutClasses.EdgeSidebarContent}:hover`]: {
+ "--SidebarContent-width": "var(--EdgeSidebar-permanentWidth)",
+ "--SidebarContent-transitionDelay": expandConfig.delay,
+ "--SidebarContent-shadow": `var(--collapsed, ${expandConfig.shadow}, var(--EdgeSidebar-sidelineWidth) 0 var(--EdgeSidebar-sidelineColor))`,
+ },
+ }),
+ };
+}
+
+export function applyEdgeSidebarStyles(params: {
+ theme: Theme;
+ config: Partial<
+ Record
+ >;
+}) {
+ const { config, theme } = params;
+ let autoCollapseStyles = {};
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const responsive: any = {};
+ (Object.keys(config) as Array)
+ .sort((a, b) => theme.breakpoints.values[a] - theme.breakpoints.values[b])
+ .forEach((breakpoint) => {
+ const variantConfig = config[breakpoint];
+ if (variantConfig) {
+ const { variant, ...params } = variantConfig;
+ if (variant === "permanent") {
+ if ("autoCollapse" in variantConfig && variantConfig.autoCollapse) {
+ let nextBreakpoint;
+ if (typeof variantConfig.autoCollapse === "number") {
+ nextBreakpoint = variantConfig.autoCollapse + 0.01;
+ } else if (
+ theme.breakpoints.keys.includes(variantConfig.autoCollapse)
+ ) {
+ nextBreakpoint =
+ theme.breakpoints.keys[
+ theme.breakpoints.keys.indexOf(
+ variantConfig.autoCollapse as Breakpoint,
+ ) + 1
+ ];
+ }
+ if (!nextBreakpoint) {
+ console.warn(
+ "MUI Treasury Layout: `autoCollapse` cannot be the largest breakpoint.",
+ );
+ } else {
+ const minBreakpoint = theme.breakpoints.values[breakpoint]
+ ? breakpoint
+ : ((breakpoint.match(/\d+/g)?.[0] || 0) as number);
+ autoCollapseStyles = {
+ [`.${layoutClasses.Root}:has(&)`]: {
+ [theme.breakpoints.between(minBreakpoint, nextBreakpoint)]: {
+ "--EdgeSidebar-collapsible": "var(--collapsed)",
+ },
+ [theme.breakpoints.up(nextBreakpoint)]: {
+ "--EdgeSidebar-collapsible": "var(--uncollapsed)",
+ },
+ },
+ [theme.breakpoints.between(
+ variantConfig.autoCollapse,
+ nextBreakpoint,
+ )]: {
+ [`.${layoutClasses.Root}:has(&[${layoutAttrs.isAutoCollapseOff}])`]:
+ {
+ "--EdgeSidebar-collapsible": "var(--uncollapsed)",
+ },
+ [`.${layoutClasses.Root}:has(&) .${layoutClasses.EdgeSidebarCollapser}`]:
+ {
+ "--_autoCollapse": "1",
+ },
+ },
+ };
+ }
+ }
+ }
+ const variantStyles = {
+ temporary: applyTemporaryStyles,
+ persistent: applyPersistentStyles,
+ permanent: applyPermanentStyles,
+ }[variant](params);
+ if (theme.breakpoints.keys.includes(breakpoint)) {
+ responsive[theme.breakpoints.up(breakpoint)] = variantStyles;
+ } else {
+ responsive[breakpoint] = variantStyles;
+ }
+ }
+ });
+ return {
+ ...responsive,
+ ...autoCollapseStyles,
+ };
+}
+
+export function toggleEdgeSidebarCollapse(options: {
+ event: React.MouseEvent;
+ sidebarId?: string;
+ state?: boolean;
+ document?: Document | null;
+}) {
+ const { sidebarId } = options || {};
+ let selector = ".EdgeSidebar";
+ if (sidebarId) {
+ selector = `#${sidebarId}`;
+ }
+ internalCollapseSidebar({ ...options, selector });
+}
+
+export function toggleTemporaryEdgeSidebar(options?: {
+ sidebarId?: string;
+ state?: boolean;
+ document?: Document | null;
+}) {
+ const { sidebarId } = options || {};
+ let selector = ".EdgeSidebar";
+ if (sidebarId) {
+ selector = `#${sidebarId}`;
+ }
+ internalToggleSidebar({ ...options, selector });
+}
+
+const StyledEdgeSidebarLeft = styled(EdgeSidebarRoot)({
+ [`.${layoutClasses.Root}:has(&)`]: {
+ /** Root default settings */
+ "--EdgeSidebar-variant": "var(--permanent)",
+ "--EdgeSidebar-permanentWidth": "256px",
+ "--EdgeSidebar-collapsible": "var(--uncollapsed)",
+ "--EdgeSidebar-collapsedWidth": "80px",
+
+ /** DO NOT OVERRIDE, internal variables */
+ "--temporary": "var(--EdgeSidebar-variant,)",
+ "--permanent": "var(--EdgeSidebar-variant,)",
+ "--_permanentWidth": `var(--uncollapsed, var(--EdgeSidebar-permanentWidth))
+ var(--collapsed, var(--EdgeSidebar-collapsedWidth, 0px))`,
+ "--collapsed": "var(--EdgeSidebar-collapsible,)",
+ "--uncollapsed": "var(--EdgeSidebar-collapsible,)",
+
+ /** Collapsible feature */
+ [`.${layoutClasses.EdgeSidebarCollapser}`]: {
+ display: "var(--display, inline-flex)",
+ "--_sidebarCollapsed": "var(--collapsed, 1)",
+ [`.${layoutClasses.EdgeSidebarUncollapsedVisible}`]: {
+ display: "var(--collapsed, none) var(--uncollapsed, inline-block)",
+ },
+ [`.${layoutClasses.EdgeSidebarCollapsedVisible}`]: {
+ display: "var(--collapsed, inline-block) var(--uncollapsed, none)",
+ },
+ },
+ },
+
+ /** Collapsible feature */
+ [`.${layoutClasses.Root}:has(&[${layoutAttrs.isEdgeSidebarCollapsed}])`]: {
+ "--EdgeSidebar-collapsible": "var(--collapsed)",
+ },
+
+ /** EdgeSidebar default settings */
+ "--EdgeSidebar-anchor": "var(--anchorLeft)",
+ "--SidebarContent-width": "var(--_permanentWidth, 0px)",
+ "--_temporary": "var(--temporary)",
+ "--_permanent": "var(--permanent)",
+ gridArea: layoutClasses.EdgeSidebar,
+ width: `var(--temporary, 0)
+ var(--permanent, var(--_permanentWidth))`,
+ borderRight:
+ "var(--permanent, min(var(--EdgeSidebar-sidelineWidth), 1 * var(--SidebarContent-width)) solid)",
+ borderColor: "var(--EdgeSidebar-sidelineColor)",
+ "&::after": {
+ border: "inherit",
+ left: 0,
+ },
+ "&::before": {
+ display: `var(--temporary, block)
+ var(--permanent, none)`,
+ },
+ [`&:not([${layoutAttrs.isTemporaryEdgeSidebarOpen}], [${layoutAttrs.isTemporaryEdgeSidebarClosing}])`]:
+ {
+ overflow: "var(--temporary, hidden)",
+ },
+});
+
+const EdgeSidebar = React.forwardRef(
+ function EdgeSidebar({ className, ...props }, ref) {
+ return (
+
+ );
+ },
+);
+
+export default EdgeSidebar;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarContent.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarContent.tsx
new file mode 100644
index 00000000..e9c699c3
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarContent.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { layoutAttrs, layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+const StyledEdgeSidebarContent = styled("div")(({ theme }) => ({
+ display: "flex",
+ background: (theme.vars || theme).palette.background.paper,
+ boxShadow: "var(--EdgeSidebarContent-shadow, var(--SidebarContent-shadow))", // --SidebarContent-shadow is internal.
+ flexDirection: "column",
+ opacity: `var(--_temporary, var(--EdgeSidebar-temporaryOpen))
+ var(--_permanent, 1)`,
+ visibility: `var(--_temporary, hidden)
+ var(--_permanent, visible)` as any,
+ overflowX: "auto", // prevent horizontal content overflow
+ flex: 1,
+ position: "var(--_temporary, fixed) var(--_permanent, relative)" as any,
+ zIndex: 2,
+ width:
+ "var(--_temporary, var(--EdgeSidebar-temporaryWidth)) var(--_permanent, calc(var(--SidebarContent-width) - var(--EdgeSidebar-sidelineWidth, 0px)))",
+ height: "var(--_temporary, calc(100% - var(--SidebarContent-offset, 0px)))",
+ top: "var(--_temporary, var(--SidebarContent-offset, 0px))",
+ overflowY: "var(--_temporary, auto)" as any,
+ transition: `var(--_temporary, opacity 0.3s, transform 0.3s)
+ var(--_permanent, opacity 0.4s, width 0.3s var(--SidebarContent-transitionDelay, 0s), transform 0.3s var(--SidebarContent-transitionDelay, 0s), box-shadow 0.3s var(--SidebarContent-transitionDelay, 0s))`,
+ transform: `var(--_temporary, var(--anchorLeft, translateX(calc((1 - var(--EdgeSidebar-temporaryOpen)) * -100%))) var(--anchorRight, translateX(calc(var(--EdgeSidebar-temporaryOpen) * -100%))))
+ var(--_permanent, translateX(var(--EdgeSidebar-permanentSlide, 0)))`,
+ [`[${layoutAttrs.isEdgeSidebarContentHidden}] &`]: {
+ visibility: "hidden",
+ opacity: 0,
+ },
+ [`[${layoutAttrs.isTemporaryEdgeSidebarOpen}] &, [${layoutAttrs.isTemporaryEdgeSidebarClosing}] &`]:
+ {
+ visibility: "visible",
+ },
+ [`[${layoutAttrs.isTemporaryEdgeSidebarClosing}] &`]: {
+ transition: "transform 0.3s, visibility 0.3s, opacity 0.3s",
+ },
+}));
+
+const EdgeSidebarContent = React.forwardRef(
+ function EdgeSidebarContent({ className, ...props }, ref) {
+ return (
+
+ );
+ },
+);
+
+export default EdgeSidebarContent;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarRight.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarRight.tsx
new file mode 100644
index 00000000..feaa1c9f
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarRight.tsx
@@ -0,0 +1,315 @@
+import React from "react";
+import type {
+ PermanentConfig,
+ PersistentConfig,
+ TemporaryConfig,
+} from "./SharedEdgeSidebar";
+import { BoxProps } from "@mui/material/Box";
+import { Breakpoint, Theme } from "@mui/material/styles";
+import { layoutAttrs, layoutClasses } from "./layoutClasses";
+import {
+ EdgeSidebarRoot,
+ internalCollapseSidebar,
+ internalToggleSidebar,
+} from "./SharedEdgeSidebar";
+import { styled } from "./zero-styled";
+
+export function applyTemporaryRightStyles(
+ params: Omit,
+) {
+ const { width = "300px", fullHeight } = params || {};
+ return {
+ "--EdgeSidebar-temporaryWidth": "0px",
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--EdgeSidebar-R-variant": "var(--temporary-R)",
+ [`.${layoutClasses.EdgeSidebarRightCollapser}`]: {
+ display: "none",
+ },
+ },
+ [`&[${layoutAttrs.isTemporaryEdgeSidebarOpen}], &[${layoutAttrs.isTemporaryEdgeSidebarClosing}]`]:
+ {
+ "--EdgeSidebar-temporaryWidth": width,
+ },
+ ...(fullHeight
+ ? { zIndex: 5 }
+ : { "--SidebarContent-offset": "var(--Header-height)" }),
+ };
+}
+
+export function applyPersistentRightStyles(
+ params: Omit,
+) {
+ const { width = "256px", persistentBehavior = "fit" } = params || {};
+ return {
+ ...(persistentBehavior === "none" && {
+ zIndex: 2,
+ "--SidebarContent-width": `var(--collapsed-R, var(--_permanentWidth-R, 0px)) var(--uncollapsed-R, ${width})`,
+ "--EdgeSidebar-permanentSlide":
+ "var(--uncollapsed-R, -100%) var(--collapsed-R, 0)",
+ "--SidebarContent-shadow":
+ "0 0 10px rgba(0,0,0,0.1), calc(-1 * var(--EdgeSidebar-sidelineWidth)) 0 var(--EdgeSidebar-sidelineColor)",
+ "&:not([data-edge-uncollapsed])": {
+ "--SidebarContent-shadow": "none",
+ },
+ }),
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--EdgeSidebar-R-variant": "var(--permanent-R)",
+ "--EdgeSidebar-R-collapsedWidth": "0px",
+ ...(persistentBehavior === "none"
+ ? {
+ "--EdgeSidebar-R-permanentWidth": "0px",
+ }
+ : {
+ ...(width && {
+ "--EdgeSidebar-R-permanentWidth": width,
+ }),
+ }),
+ "--EdgeSidebar-R-collapsible": "var(--collapsed-R)",
+ [`.${layoutClasses.EdgeSidebarRightCollapser}`]: {
+ display: "var(--display, inline-flex)",
+ },
+ [`.${layoutClasses.TemporaryEdgeSidebarRightTrigger}`]: {
+ display: "none",
+ },
+ },
+ [`.${layoutClasses.Root}:has(&[${layoutAttrs.isEdgeSidebarUncollapsed}])`]:
+ {
+ "--EdgeSidebar-R-collapsible": "var(--uncollapsed-R)",
+ },
+ };
+}
+
+export function applyPermanentRightStyles(
+ params: Omit,
+) {
+ if ("autoCollapse" in params && !params.collapsedWidth) {
+ console.warn(
+ "MUI Treasury Layout: `collapsedWidth` is required when `autoCollapse` is enabled.",
+ );
+ }
+ const { width, collapsedWidth } = params;
+ const defaultExpandConfig = {
+ delay: "0.3s",
+ shadow: "0 0 10px rgba(0,0,0,0.1)",
+ };
+ let expandConfig: undefined | typeof defaultExpandConfig;
+ if ("expandOnHover" in params) {
+ if (params.expandOnHover === true) {
+ expandConfig = defaultExpandConfig;
+ } else {
+ expandConfig = params.expandOnHover as typeof defaultExpandConfig;
+ }
+ }
+ return {
+ "--EdgeSidebar-permanentSlide": "0", // to override persistent styles
+ "--SidebarContent-shadow": "none",
+ "--SidebarContent-width": "var(--_permanentWidth-R, 0px)",
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--EdgeSidebar-R-variant": "var(--permanent-R)",
+ ...(width && {
+ "--EdgeSidebar-R-permanentWidth": width,
+ }),
+ ...(collapsedWidth && {
+ "--EdgeSidebar-R-collapsedWidth": collapsedWidth,
+ }),
+ [`.${layoutClasses.EdgeSidebarRightCollapser}`]: {
+ display: "var(--display, inline-flex)",
+ },
+ [`.${layoutClasses.TemporaryEdgeSidebarRightTrigger}`]: {
+ display: "none",
+ },
+ },
+ ...(expandConfig && {
+ [`& .${layoutClasses.EdgeSidebarContent}:hover`]: {
+ "--SidebarContent-width": "var(--EdgeSidebar-R-permanentWidth)",
+ "--SidebarContent-transitionDelay": expandConfig.delay,
+ "--SidebarContent-shadow": `var(--collapsed-R, ${expandConfig.shadow}, calc(-1 * var(--EdgeSidebar-sidelineWidth)) 0 var(--EdgeSidebar-sidelineColor))`,
+ "--EdgeSidebar-permanentSlide":
+ "var(--collapsed-R, calc(var(--EdgeSidebar-R-collapsedWidth) - var(--SidebarContent-width))) var(--uncollapsed-R, 0)",
+ },
+ }),
+ };
+}
+
+export function toggleEdgeSidebarRightCollapse(options: {
+ event: React.MouseEvent;
+ sidebarId?: string;
+ state?: boolean;
+ document?: Document | null;
+}) {
+ const { sidebarId } = options || {};
+ let selector = ".EdgeSidebar-R";
+ if (sidebarId) {
+ selector = `#${sidebarId}`;
+ }
+ internalCollapseSidebar({ ...options, selector });
+}
+
+export function toggleTemporaryEdgeSidebarRight(options?: {
+ sidebarId?: string;
+ state?: boolean;
+ document?: Document | null;
+}) {
+ const { sidebarId } = options || {};
+ let selector = ".EdgeSidebar-R";
+ if (sidebarId) {
+ selector = `#${sidebarId}`;
+ }
+ internalToggleSidebar({ ...options, selector });
+}
+
+export function applyEdgeSidebarRightStyles(params: {
+ theme: Theme;
+ config: Partial<
+ Record
+ >;
+}) {
+ const { config, theme } = params;
+ let autoCollapseStyles = {};
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const responsive: any = {};
+ (Object.keys(config) as Array)
+ .sort((a, b) => theme.breakpoints.values[a] - theme.breakpoints.values[b])
+ .forEach((breakpoint) => {
+ const variantConfig = config[breakpoint];
+ if (variantConfig) {
+ const { variant, ...params } = variantConfig;
+ if (variant === "permanent") {
+ if ("autoCollapse" in variantConfig && variantConfig.autoCollapse) {
+ let nextBreakpoint;
+ if (typeof variantConfig.autoCollapse === "number") {
+ nextBreakpoint = variantConfig.autoCollapse + 0.01;
+ } else if (
+ theme.breakpoints.keys.includes(variantConfig.autoCollapse)
+ ) {
+ nextBreakpoint =
+ theme.breakpoints.keys[
+ theme.breakpoints.keys.indexOf(
+ variantConfig.autoCollapse as Breakpoint,
+ ) + 1
+ ];
+ }
+ if (!nextBreakpoint) {
+ console.warn(
+ "MUI Treasury Layout: `autoCollapse` cannot be the largest breakpoint.",
+ );
+ } else {
+ const minBreakpoint = theme.breakpoints.values[breakpoint]
+ ? breakpoint
+ : ((breakpoint.match(/\d+/g)?.[0] || 0) as number);
+ autoCollapseStyles = {
+ [`.${layoutClasses.Root}:has(&)`]: {
+ [theme.breakpoints.between(minBreakpoint, nextBreakpoint)]: {
+ "--EdgeSidebar-R-collapsible": "var(--collapsed-R)",
+ },
+ [theme.breakpoints.up(nextBreakpoint)]: {
+ "--EdgeSidebar-R-collapsible": "var(--uncollapsed-R)",
+ },
+ },
+ [theme.breakpoints.between(
+ variantConfig.autoCollapse,
+ nextBreakpoint,
+ )]: {
+ [`.${layoutClasses.Root}:has(&[${layoutAttrs.isAutoCollapseOff}])`]:
+ {
+ "--EdgeSidebar-R-collapsible": "var(--uncollapsed-R)",
+ },
+ [`.${layoutClasses.Root}:has(&) .${layoutClasses.EdgeSidebarRightCollapser}`]:
+ {
+ "--_autoCollapse": "1",
+ },
+ },
+ };
+ }
+ }
+ }
+ const variantStyles = {
+ temporary: applyTemporaryRightStyles,
+ persistent: applyPersistentRightStyles,
+ permanent: applyPermanentRightStyles,
+ }[variant](params);
+ if (theme.breakpoints.keys.includes(breakpoint)) {
+ responsive[theme.breakpoints.up(breakpoint)] = variantStyles;
+ } else {
+ responsive[breakpoint] = variantStyles;
+ }
+ }
+ });
+ return {
+ ...responsive,
+ ...autoCollapseStyles,
+ };
+}
+
+const StyledEdgeSidebarRight = styled(EdgeSidebarRoot)({
+ [`.${layoutClasses.Root}:has(&)`]: {
+ /** Root default settings */
+ "--EdgeSidebar-R-variant": "var(--permanent-R)",
+ "--EdgeSidebar-R-permanentWidth": "256px",
+ "--EdgeSidebar-R-collapsible": "var(--uncollapsed-R)",
+
+ /** DO NOT OVERRIDE, internal variables */
+ "--temporary-R": "var(--EdgeSidebar-R-variant,)",
+ "--permanent-R": "var(--EdgeSidebar-R-variant,)",
+ "--_permanentWidth-R": `var(--uncollapsed-R, var(--EdgeSidebar-R-permanentWidth))
+ var(--collapsed-R, var(--EdgeSidebar-R-collapsedWidth, 0px))`,
+ "--collapsed-R": "var(--EdgeSidebar-R-collapsible,)",
+ "--uncollapsed-R": "var(--EdgeSidebar-R-collapsible,)",
+
+ /** Collapsible feature */
+ [`.${layoutClasses.EdgeSidebarRightCollapser}`]: {
+ display: "var(--display, inline-flex)",
+ "--_sidebarCollapsed": "var(--collapsed-R, 1)",
+ [`.${layoutClasses.EdgeSidebarUncollapsedVisible}`]: {
+ display: "var(--collapsed-R, none) var(--uncollapsed-R, inline-block)",
+ },
+ [`.${layoutClasses.EdgeSidebarCollapsedVisible}`]: {
+ display: "var(--collapsed-R, inline-block) var(--uncollapsed-R, none)",
+ },
+ },
+ },
+
+ /** Collapsible feature */
+ [`.${layoutClasses.Root}:has(&[${layoutAttrs.isEdgeSidebarCollapsed}])`]: {
+ "--EdgeSidebar-R-collapsible": "var(--collapsed-R)",
+ },
+
+ /** EdgeSidebar default settings */
+ "--EdgeSidebar-anchor": "var(--anchorRight)",
+ "--SidebarContent-width": "var(--_permanentWidth-R, 0px)",
+ "--_temporary": "var(--temporary-R)",
+ "--_permanent": "var(--permanent-R)",
+ gridArea: layoutClasses.EdgeSidebarRight,
+ width: `var(--temporary-R, 0)
+ var(--permanent-R, var(--_permanentWidth-R))`,
+ borderLeft:
+ "var(--permanent, min(var(--EdgeSidebar-sidelineWidth), 1 * var(--SidebarContent-width)) solid)",
+ borderColor: "var(--EdgeSidebar-sidelineColor)",
+ "&::after": {
+ border: "inherit",
+ right: 0, // prevent Root overflow
+ },
+ "&::before": {
+ display: `var(--temporary-R, block)
+ var(--permanent-R, none)`,
+ },
+ [`&:not([${layoutAttrs.isTemporaryEdgeSidebarOpen}], [${layoutAttrs.isTemporaryEdgeSidebarClosing}])`]:
+ {
+ overflow: "var(--temporary-R, hidden)",
+ },
+});
+
+const EdgeSidebarRight = React.forwardRef(
+ function EdgeSidebar({ className, ...props }, ref) {
+ return (
+
+ );
+ },
+);
+
+export default EdgeSidebarRight;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeTemporaryClose.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeTemporaryClose.tsx
new file mode 100644
index 00000000..d1fc115f
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeTemporaryClose.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import { SxProps } from "@mui/material/styles";
+import { layoutAttrs, layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+const StyledEdgeTemporaryClose = styled("button")({
+ display: "var(--_temporary, flex) var(--_permanent, none)",
+ visibility: "hidden",
+ opacity: 0,
+ transition: "0.3s",
+ position: "fixed",
+ top: "calc(0.875rem + var(--SidebarContent-offset, 0px))",
+ right: "var(--anchorLeft, 0.875rem)",
+ left: "var(--anchorRight, 0.875rem)",
+ zIndex: 2,
+ width: 40,
+ height: 40,
+ color: "white",
+ cursor: "pointer",
+ backgroundColor: "#999",
+ borderRadius: "40px",
+ alignItems: "center",
+ justifyContent: "center",
+ border: "none",
+ "& svg": {
+ width: "1.5em",
+ height: "1.5em",
+ },
+ [`[${layoutAttrs.isTemporaryEdgeSidebarOpen}] &`]: {
+ visibility: "visible",
+ opacity: 1,
+ },
+});
+
+const EdgeTemporaryClose = React.forwardRef<
+ HTMLButtonElement,
+ JSX.IntrinsicElements["button"] & { sx?: SxProps; sidebarId?: string }
+>(function EdgeTemporaryClose(
+ { className, sidebarId, children, ...props },
+ ref,
+) {
+ return (
+
+ {children || (
+
+ )}
+
+ );
+});
+
+export default EdgeTemporaryClose;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Footer.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Footer.tsx
new file mode 100644
index 00000000..b9ec35dc
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Footer.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+const StyledFooter = styled("footer")(({ theme }) => ({
+ gridArea: layoutClasses.Footer,
+ transition: "all 225ms cubic-bezier(0.0, 0, 0.2, 1) 0ms, color 0s",
+ background: (theme.vars || theme).palette.background.paper,
+ borderTop: `1px solid ${(theme.vars || theme).palette.divider}`,
+}));
+
+const Footer = React.forwardRef(function Footer(
+ { className, ...props },
+ ref,
+) {
+ return (
+
+ );
+});
+
+export default Footer;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Header.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Header.tsx
new file mode 100644
index 00000000..93accb53
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Header.tsx
@@ -0,0 +1,66 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { Breakpoint } from "@mui/material/styles";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+export function applyHeaderStyles(params?: {
+ height: string | Partial>;
+ fullWidth?: boolean | Breakpoint;
+}) {
+ const { height, fullWidth } = params || {};
+ const clip = `
+ "${layoutClasses.Header} ${layoutClasses.Header} ${layoutClasses.Header}"
+ "${layoutClasses.EdgeSidebar} ${layoutClasses.Content} ${layoutClasses.EdgeSidebarRight}"
+ "${layoutClasses.EdgeSidebar} ${layoutClasses.Footer} ${layoutClasses.EdgeSidebarRight}"
+ `;
+ return {
+ height,
+ ...(fullWidth && { zIndex: 3 }),
+ [`.${layoutClasses.Root}:has(&)`]: {
+ "--Header-height": height,
+ ...(fullWidth && {
+ gridTemplateAreas:
+ typeof fullWidth === "string"
+ ? {
+ [fullWidth]: clip,
+ }
+ : clip,
+ "--Header-clipHeight":
+ typeof fullWidth === "string"
+ ? {
+ [fullWidth]: "var(--Header-height)",
+ }
+ : "var(--Header-height)",
+ }),
+ },
+ };
+}
+
+const StyledHeader = styled("header")(({ theme }) => ({
+ gridArea: layoutClasses.Header,
+ height: 56, // better than `min-height` because user can set height to 0
+ alignContent: "center",
+ display: "flex",
+ alignItems: "center",
+ top: 0, // for position sticky to work
+ position: "sticky",
+ background: (theme.vars || theme).palette.background.paper,
+ borderBottom: `1px solid ${(theme.vars || theme).palette.divider}`,
+}));
+
+const Header = React.forwardRef(function Header(
+ { className, ...props },
+ ref,
+) {
+ return (
+
+ );
+});
+
+export default Header;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetAvoidingView.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetAvoidingView.tsx
new file mode 100644
index 00000000..0b690d5d
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetAvoidingView.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+const StyledInsetAvoidingView = styled("div")({
+ marginRight: "var(--InsetSidebarR-width)",
+ marginLeft: "var(--InsetSidebarL-width)",
+});
+
+const InsetAvoidingView = React.forwardRef(
+ function InsetAvoidingView({ className, ...props }, ref) {
+ return (
+
+ );
+ },
+);
+
+export default InsetAvoidingView;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebar.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebar.tsx
new file mode 100644
index 00000000..a970f56e
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebar.tsx
@@ -0,0 +1,90 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { Breakpoint } from "@mui/material/styles";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+export function applyInsetSidebarStyles(params: {
+ width: string | Partial>;
+ /**
+ * The CSS position property of the sidebar.
+ * @default "sticky"
+ */
+ position?:
+ | "fixed"
+ | "absolute"
+ | "sticky"
+ | Record;
+}) {
+ const { width, position = "sticky" } = params;
+ let positionStyles: Record = {};
+ if (position && typeof position !== "string") {
+ Object.entries(position).forEach(([key, value]) => {
+ positionStyles[key] = `var(--${value},)`;
+ });
+ }
+ return {
+ width,
+ // For `InsetAvoidingView`
+ [`.${layoutClasses.Root}:has(&:not(:last-child))`]: {
+ [`--InsetSidebarL-width`]: width,
+ },
+ [`.${layoutClasses.Root}:has(&:last-child)`]: {
+ [`--InsetSidebarR-width`]: width,
+ },
+ ...(typeof width !== "string" && {
+ display: {
+ xs: "none",
+ [Object.keys(width)[0]]: "block",
+ },
+ }),
+ "--InsetSidebar-position":
+ typeof position === "string" ? `var(--${position},)` : positionStyles,
+ };
+}
+
+const InsetSidebarRoot = styled("aside")({
+ "--InsetSidebar-position": "var(--sticky)",
+ /** DO NOT OVERRIDE, internal variables */
+ "--sticky": "var(--InsetSidebar-position,)",
+ "--fixed": "var(--InsetSidebar-position,)",
+ "--absolute": "var(--InsetSidebar-position,)",
+ "--anchor-right": "var(--InsetSidebar-anchor,)",
+ "--anchor-left": "var(--InsetSidebar-anchor,)",
+ width: "200px",
+ position: "relative",
+ flexShrink: 0,
+ "&:not(:last-child)": {
+ "--InsetSidebar-anchor": "var(--anchor-left)",
+ },
+ "&:last-child": {
+ "--InsetSidebar-anchor": "var(--anchor-right)",
+ },
+ "*:has(> &)": {
+ display: "flex",
+ flexFlow: "row nowrap",
+ flexGrow: 1,
+ },
+ // TODO: open an issue on Pigment CSS, :where(:not(...)) is not working
+ [`*:has(> &) > :not([class*="${layoutClasses.InsetSidebar}"])`]: {
+ flexGrow: 1,
+ overflow: "auto",
+ },
+});
+
+const InsetSidebar = React.forwardRef(
+ function InsetSidebar({ className, children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export default InsetSidebar;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebarContent.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebarContent.tsx
new file mode 100644
index 00000000..3a3b8f1c
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebarContent.tsx
@@ -0,0 +1,46 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+const InsetSidebarContentRoot = styled("div")(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ backgroundColor: "inherit",
+ overflow: "auto",
+ background: (theme.vars || theme).palette.background.paper,
+ boxSizing:
+ "var(--sticky, border-box) var(--fixed, content-box) var(--absolute, border-box)" as any,
+ position:
+ "var(--sticky, sticky) var(--fixed, fixed) var(--absolute, absolute)" as any,
+ height:
+ "var(--sticky, initial) var(--fixed, calc(100% - var(--Header-height, 0px))) var(--absolute, calc(var(--Root-height, 100vh) - var(--Content-insetBottom, 0px) - var(--Header-height, 0px)))",
+ width: "var(--sticky, inherit) var(--fixed, inherit) var(--absolute, 100%)",
+ top: 0,
+ marginLeft:
+ "var(--fixed, var(--anchor-left, -9999px)) var(--absolute, initial) var(--sticky, initial)",
+ paddingLeft:
+ "var(--fixed, var(--anchor-left, 9999px)) var(--absolute, initial) var(--sticky, initial)",
+ marginRight:
+ "var(--fixed, var(--anchor-right, -9999px)) var(--absolute, initial) var(--sticky, initial)",
+ paddingRight:
+ "var(--fixed, var(--anchor-right, 9999px)) var(--absolute, initial) var(--sticky, initial)",
+ marginTop: "var(--fixed, var(--Header-height))",
+}));
+
+const InsetSidebarContent = React.forwardRef(
+ function InsetSidebarContent({ className, children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export default InsetSidebarContent;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Root.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Root.tsx
new file mode 100644
index 00000000..432d80e8
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Root.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { BoxProps } from "@mui/material/Box";
+import { Breakpoint } from "@mui/material/styles";
+import { layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+export function applyRootStyles(params?: {
+ height?: string | Record | Record;
+ fixedHeight?: boolean;
+}) {
+ const { height, fixedHeight } = params || {};
+ return {
+ ...(height && {
+ "--Root-height": height,
+ }),
+ ...(fixedHeight && {
+ maxHeight: "var(--Root-height)",
+ }),
+ };
+}
+
+const StyledRoot = styled("div")(({ theme }) => ({
+ "--Root-height": "100lvh",
+ "--EdgeSidebar-sidelineWidth": "1px",
+ "--EdgeSidebar-sidelineColor": (theme.vars || theme).palette.divider,
+ backgroundColor: (theme.vars || theme).palette.background.paper,
+ minHeight: "var(--Root-height)",
+ display: "grid",
+ position: "relative",
+ transition: "grid-template-columns 0.3s",
+ gridTemplateRows: "auto 1fr",
+ gridTemplateColumns:
+ "var(--_start-col, 0px) minmax(0, 1fr) var(--_end-col, 0px)", // minmax(0, 1fr) is used over `1fr` to prevent root horizontal overflow
+ gridTemplateAreas: `
+ "${layoutClasses.EdgeSidebar} ${layoutClasses.Header} ${layoutClasses.EdgeSidebarRight}"
+ "${layoutClasses.EdgeSidebar} ${layoutClasses.Content} ${layoutClasses.EdgeSidebarRight}"
+ "${layoutClasses.EdgeSidebar} ${layoutClasses.Footer} ${layoutClasses.EdgeSidebarRight}"
+ `,
+
+ [`&:has(.${layoutClasses.EdgeSidebar})`]: {
+ "--_start-col": "max-content",
+ "--EdgeSidebar-temporaryOpen": "0",
+ },
+ [`&:has(.${layoutClasses.EdgeSidebarRight})`]: {
+ "--_end-col": "max-content",
+ "--EdgeSidebar-temporaryOpen": "0",
+ },
+}));
+
+const Root = React.forwardRef(function Root(
+ { className, ...props },
+ ref,
+) {
+ return (
+
+ );
+});
+
+export default Root;
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/SharedEdgeSidebar.tsx b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/SharedEdgeSidebar.tsx
new file mode 100644
index 00000000..2ca370b5
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/SharedEdgeSidebar.tsx
@@ -0,0 +1,174 @@
+import { Breakpoint } from "@mui/material/styles";
+import { layoutAttrs, layoutClasses } from "./layoutClasses";
+import { styled } from "./zero-styled";
+
+export type TemporaryConfig = {
+ variant: "temporary";
+ width?: string;
+ fullHeight?: boolean;
+};
+export type PersistentConfig = {
+ variant: "persistent";
+ /**
+ * @default "fit"
+ */
+ persistentBehavior?: "fit" | "none";
+ width?: string;
+};
+export type PermanentConfig = {
+ variant: "permanent";
+ width?: string;
+ /**
+ * When the viewport shrink to the provided breakpoint, the EdgeSidebar will collapse automatically.
+ * Must set `collapsedWidth` to specify the width of the collapsed sidebar
+ */
+ autoCollapse?: Breakpoint | number;
+ /**
+ * Required `autoCollapse`, the width of the sidebar after collapsed.
+ */
+ collapsedWidth?: string;
+ /**
+ * Required `autoCollapse`, if set the sidebar content will expand on hover.
+ */
+ expandOnHover?:
+ | true
+ | {
+ delay?: string;
+ shadow?: string;
+ };
+};
+
+export function internalCollapseSidebar(options: {
+ event: React.MouseEvent;
+ selector: string;
+ state?: boolean;
+ document?: Document | null;
+}) {
+ const { state, document: d, selector, event } = options || {};
+ const doc = d ?? document;
+ const sidebar = doc.querySelector(selector) as HTMLElement;
+ if (sidebar) {
+ const currentCollapsed =
+ window
+ .getComputedStyle(event.target as Element)
+ .getPropertyValue("--_sidebarCollapsed") === "1";
+ const nextCollapsed = state === undefined ? !currentCollapsed : state;
+ const autoCollapse =
+ window
+ .getComputedStyle(event.target as Element)
+ .getPropertyValue("--_autoCollapse") === "1";
+ if (autoCollapse) {
+ // toggle within autoCollapse breakpoint
+ if (nextCollapsed) {
+ sidebar.removeAttribute(layoutAttrs.isAutoCollapseOff);
+ sidebar.removeAttribute(layoutAttrs.isEdgeSidebarUncollapsed);
+ } else {
+ sidebar.setAttribute(layoutAttrs.isAutoCollapseOff, "");
+ sidebar.removeAttribute(layoutAttrs.isEdgeSidebarCollapsed);
+ }
+ } else {
+ if (nextCollapsed) {
+ sidebar.setAttribute(layoutAttrs.isEdgeSidebarCollapsed, "");
+ sidebar.removeAttribute(layoutAttrs.isEdgeSidebarUncollapsed);
+ sidebar.removeAttribute(layoutAttrs.isAutoCollapseOff);
+ } else {
+ sidebar.removeAttribute(layoutAttrs.isEdgeSidebarCollapsed);
+ sidebar.setAttribute(layoutAttrs.isEdgeSidebarUncollapsed, "");
+ }
+ }
+ }
+}
+
+export function internalToggleSidebar(options: {
+ selector: string;
+ state?: boolean;
+ document?: Document | null;
+}) {
+ const { state, document: d, selector } = options || {};
+ const doc = d ?? document;
+ const sidebar = doc.querySelector(selector) as HTMLDivElement | null;
+ if (sidebar) {
+ const currentOpen =
+ sidebar.getAttribute(layoutAttrs.isTemporaryEdgeSidebarOpen) !== null;
+ const nextOpen = state === undefined ? !currentOpen : state;
+ if (nextOpen) {
+ sidebar.setAttribute(layoutAttrs.isTemporaryEdgeSidebarOpen, "");
+ sidebar.style.setProperty("--EdgeSidebar-temporaryOpen", "1");
+ // @ts-expect-error Material UI issue
+ function handleOutsideClick(event: MouseEvent) {
+ const closer = doc.querySelector(
+ `.${layoutClasses.TemporaryEdgeSidebarClose}`,
+ ) as HTMLButtonElement;
+ if (
+ // clicking on the backdrop (psuedo element of sidebar) will close the sidebar
+ event.target === sidebar ||
+ // clicking on the closer button will close the sidebar
+ (closer && closer.contains(event.target as Node))
+ ) {
+ internalToggleSidebar({
+ ...options,
+ state: false,
+ });
+ doc.removeEventListener?.("click", handleOutsideClick);
+ }
+ }
+ setTimeout(() => {
+ // prevent the `handleOutsideClick` to be called immediately
+ doc.addEventListener?.("click", handleOutsideClick);
+ }, 0);
+
+ // TODO: add a way to close the sidebar by swiping
+ // TODO: add a way to close the sidebar by pressing ESC
+ } else {
+ sidebar.removeAttribute(layoutAttrs.isTemporaryEdgeSidebarOpen);
+ sidebar.setAttribute(layoutAttrs.isTemporaryEdgeSidebarClosing, "");
+ setTimeout(() => {
+ sidebar.removeAttribute(layoutAttrs.isTemporaryEdgeSidebarClosing);
+ }, 300);
+ sidebar.style.setProperty("--EdgeSidebar-temporaryOpen", "");
+ }
+ }
+}
+
+export const EdgeSidebarRoot = styled("div")({
+ "--anchorLeft": "var(--EdgeSidebar-anchor,)",
+ "--anchorRight": "var(--EdgeSidebar-anchor,)",
+ transition: "width 0.3s",
+ display: "flex",
+ flexDirection: "column",
+ // ==============================
+ // To keep the EdgeSidebar fixed when the Content is scrollable
+ position: "var(--_permanent, sticky)" as any,
+ top: "var(--_permanent, var(--Header-clipHeight, 0px))",
+ zIndex: "var(--_temporary, 2) var(--_permanent, 1)",
+ height:
+ "var(--_permanent, calc(var(--Root-height) - var(--Header-clipHeight, 0px)))",
+ // ==============================
+ "&::before": {
+ position: "absolute",
+ content: '""',
+ inset: 0,
+ backgroundColor: "rgba(0, 0, 0, 0.48)",
+ backdropFilter: "blur(4px)",
+ zIndex: 1,
+ transition: "opacity 0.4s, visibility 0.4s",
+ visibility: "hidden",
+ opacity: "var(--EdgeSidebar-temporaryOpen, 0)",
+ },
+ [`&[${layoutAttrs.isTemporaryEdgeSidebarOpen}]`]: {
+ "&::before": {
+ visibility: "visible",
+ },
+ },
+ [`html:has(&[${layoutAttrs.isTemporaryEdgeSidebarOpen}])`]: {
+ overflow: "hidden",
+ },
+ "&::after": {
+ position: "absolute",
+ content: '""',
+ display: "block",
+ width: "var(--_permanent, var(--SidebarContent-width))",
+ height: "var(--Header-clipHeight)",
+ top: "calc(-1 * var(--Header-clipHeight))",
+ },
+});
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/index.ts b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/index.ts
new file mode 100644
index 00000000..8b60153c
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/index.ts
@@ -0,0 +1,23 @@
+export { default as Content } from "./Content";
+export * from "./Content";
+export { default as EdgeTemporaryClose } from "./EdgeTemporaryClose";
+export * from "./EdgeTemporaryClose";
+export { default as EdgeSidebar } from "./EdgeSidebar";
+export * from "./EdgeSidebar";
+export { default as EdgeSidebarContent } from "./EdgeSidebarContent";
+export * from "./EdgeSidebarContent";
+export { default as EdgeSidebarRight } from "./EdgeSidebarRight";
+export * from "./EdgeSidebarRight";
+export { default as Header } from "./Header";
+export * from "./Header";
+export { default as Footer } from "./Footer";
+export * from "./Footer";
+export { default as Root } from "./Root";
+export * from "./Root";
+export { default as InsetAvoidingView } from "./InsetAvoidingView";
+export * from "./InsetAvoidingView";
+export { default as InsetSidebar } from "./InsetSidebar";
+export * from "./InsetSidebar";
+export { default as InsetSidebarContent } from "./InsetSidebarContent";
+export * from "./InsetSidebarContent";
+export { layoutClasses, layoutAttrs } from "./layoutClasses";
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/layoutClasses.ts b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/layoutClasses.ts
new file mode 100644
index 00000000..06ac11f7
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/layoutClasses.ts
@@ -0,0 +1,33 @@
+export const layoutClasses = {
+ Root: "Root",
+ Content: "Content",
+ EdgeSidebar: "EdgeSidebar",
+ EdgeSidebarContent: "EdgeSidebarContent",
+ EdgeSidebarCollapser: "EdgeSidebar-collapser",
+ TemporaryEdgeSidebarTrigger: "EdgeSidebar-trigger",
+
+ TemporaryEdgeSidebarClose: "EdgeTemporaryClose",
+ EdgeSidebarCollapsedVisible: "Icon-uncollapse",
+ EdgeSidebarUncollapsedVisible: "Icon-collapse",
+
+ EdgeSidebarRight: "EdgeSidebar-R",
+ EdgeSidebarRightCollapser: "EdgeSidebar-R-collapser",
+ TemporaryEdgeSidebarRightTrigger: "EdgeSidebar-R-trigger",
+
+ Footer: "Footer",
+
+ Header: "Header",
+
+ InsetSidebar: "InsetSidebar",
+ InsetAvoidingView: "InsetAvoidingView",
+ InsetSidebarContent: "InsetSidebarContent",
+};
+
+export const layoutAttrs = {
+ isTemporaryEdgeSidebarOpen: "data-temporary-open",
+ isTemporaryEdgeSidebarClosing: "data-mobile-closing",
+ isEdgeSidebarUncollapsed: "data-edge-uncollapsed",
+ isEdgeSidebarCollapsed: "data-edge-collapsed",
+ isAutoCollapseOff: "data-auto-collapse-off",
+ isEdgeSidebarContentHidden: "data-sidebar-hidden",
+};
diff --git a/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/zero-styled/index.ts b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/zero-styled/index.ts
new file mode 100644
index 00000000..9086c1a4
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/zero-styled/index.ts
@@ -0,0 +1 @@
+export { styled } from "@mui/material/styles";
diff --git a/examples/mui-treasury-layout-nextjs/tsconfig.json b/examples/mui-treasury-layout-nextjs/tsconfig.json
new file mode 100644
index 00000000..7b285893
--- /dev/null
+++ b/examples/mui-treasury-layout-nextjs/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}