From c4d039f44d6069eed6eb93dfca797dfa00c6fe01 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 14 Aug 2024 14:08:41 +0700 Subject: [PATCH] feat(examples): add mui-treasury-layout-nextjs --- .gitignore | 3 +- .../mui-treasury-layout-nextjs/.gitignore | 36 ++ examples/mui-treasury-layout-nextjs/README.md | 45 +++ .../next.config.mjs | 4 + .../mui-treasury-layout-nextjs/package.json | 27 ++ .../public/next.svg | 1 + .../src/app/MuiProvider.tsx | 30 ++ .../src/app/favicon.ico | Bin 0 -> 25931 bytes .../src/app/layout.tsx | 21 ++ .../src/app/page.tsx | 97 ++++++ .../mui-treasury/layout-core-v6/Content.tsx | 31 ++ .../layout-core-v6/EdgeSidebar.tsx | 305 +++++++++++++++++ .../layout-core-v6/EdgeSidebarContent.tsx | 54 +++ .../layout-core-v6/EdgeSidebarRight.tsx | 315 ++++++++++++++++++ .../layout-core-v6/EdgeTemporaryClose.tsx | 67 ++++ .../mui-treasury/layout-core-v6/Footer.tsx | 27 ++ .../mui-treasury/layout-core-v6/Header.tsx | 66 ++++ .../layout-core-v6/InsetAvoidingView.tsx | 24 ++ .../layout-core-v6/InsetSidebar.tsx | 90 +++++ .../layout-core-v6/InsetSidebarContent.tsx | 46 +++ .../src/mui-treasury/layout-core-v6/Root.tsx | 64 ++++ .../layout-core-v6/SharedEdgeSidebar.tsx | 174 ++++++++++ .../src/mui-treasury/layout-core-v6/index.ts | 23 ++ .../layout-core-v6/layoutClasses.ts | 33 ++ .../layout-core-v6/zero-styled/index.ts | 1 + .../mui-treasury-layout-nextjs/tsconfig.json | 26 ++ 26 files changed, 1609 insertions(+), 1 deletion(-) create mode 100644 examples/mui-treasury-layout-nextjs/.gitignore create mode 100644 examples/mui-treasury-layout-nextjs/README.md create mode 100644 examples/mui-treasury-layout-nextjs/next.config.mjs create mode 100644 examples/mui-treasury-layout-nextjs/package.json create mode 100644 examples/mui-treasury-layout-nextjs/public/next.svg create mode 100644 examples/mui-treasury-layout-nextjs/src/app/MuiProvider.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/app/favicon.ico create mode 100644 examples/mui-treasury-layout-nextjs/src/app/layout.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/app/page.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Content.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebar.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarContent.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeSidebarRight.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/EdgeTemporaryClose.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Footer.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Header.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetAvoidingView.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebar.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/InsetSidebarContent.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/Root.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/SharedEdgeSidebar.tsx create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/index.ts create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/layoutClasses.ts create mode 100644 examples/mui-treasury-layout-nextjs/src/mui-treasury/layout-core-v6/zero-styled/index.ts create mode 100644 examples/mui-treasury-layout-nextjs/tsconfig.json 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/MuiProvider.tsx b/examples/mui-treasury-layout-nextjs/src/app/MuiProvider.tsx new file mode 100644 index 00000000..3b885f1f --- /dev/null +++ b/examples/mui-treasury-layout-nextjs/src/app/MuiProvider.tsx @@ -0,0 +1,30 @@ +"use client"; + +import React from "react"; +import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter"; +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 MuiProvider({ + 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 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 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..2ff9ad8b --- /dev/null +++ b/examples/mui-treasury-layout-nextjs/src/app/layout.tsx @@ -0,0 +1,21 @@ +import type { Metadata } from "next"; +import MuiProvider from "./MuiProvider"; + +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 }} + > + + + Vercel Logo +
+ ({ + ...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"] +}