{"id":14957337,"url":"https://github.com/moxystudio/next-layout","last_synced_at":"2025-04-05T18:10:26.821Z","repository":{"id":37651295,"uuid":"237655977","full_name":"moxystudio/next-layout","owner":"moxystudio","description":"Add persistent and nested layouts to your Next.js projects in a declarative way","archived":false,"fork":false,"pushed_at":"2021-09-10T07:01:22.000Z","size":456,"stargazers_count":258,"open_issues_count":6,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T17:13:06.429Z","etag":null,"topics":["layout","nested","next","page","react"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moxystudio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-01T18:06:42.000Z","updated_at":"2024-08-31T02:39:05.000Z","dependencies_parsed_at":"2022-09-07T22:00:25.122Z","dependency_job_id":null,"html_url":"https://github.com/moxystudio/next-layout","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxystudio%2Fnext-layout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxystudio%2Fnext-layout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxystudio%2Fnext-layout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxystudio%2Fnext-layout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moxystudio","download_url":"https://codeload.github.com/moxystudio/next-layout/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247378149,"owners_count":20929297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["layout","nested","next","page","react"],"created_at":"2024-09-24T13:14:44.618Z","updated_at":"2025-04-05T18:10:26.778Z","avatar_url":"https://github.com/moxystudio.png","language":"JavaScript","readme":"# next-layout\n\n[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][build-status-image]][build-status-url] [![Coverage Status][codecov-image]][codecov-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url]\n\n[npm-url]:https://npmjs.org/package/@moxy/next-layout\n[downloads-image]:https://img.shields.io/npm/dm/@moxy/next-layout.svg\n[npm-image]:https://img.shields.io/npm/v/@moxy/next-layout.svg\n[build-status-url]:https://github.com/moxystudio/next-layout/actions\n[build-status-image]:https://img.shields.io/github/workflow/status/moxystudio/next-layout/Node%20CI/master\n[codecov-url]:https://codecov.io/gh/moxystudio/next-layout\n[codecov-image]:https://img.shields.io/codecov/c/github/moxystudio/next-layout/master.svg\n[david-dm-url]:https://david-dm.org/moxystudio/next-layout\n[david-dm-image]:https://img.shields.io/david/moxystudio/next-layout.svg\n[david-dm-dev-url]:https://david-dm.org/moxystudio/next-layout?type=dev\n[david-dm-dev-image]:https://img.shields.io/david/dev/moxystudio/next-layout.svg\n\nAdd persistent and nested layouts to your Next.js projects in a declarative way.\n\n## Installation\n\n```sh\n$ npm install @moxy/next-layout\n```\n\nThis library is written in modern JavaScript and is published in both CommonJS and ES module transpiled variants. If you target older browsers please make sure to transpile accordingly.\n\n## Motivation\n\nNext.js projects usually have the need to have one or more layouts. Layouts are the \"shell\" of your app and usually contain navigation elements, such as an header and a footer. In more complex projects, you might also need to have nested layouts which are often associated with nested routes.\n\nIn the ideal scenario, each page would be able to say which layout they want to use, including tweaking its properties dynamically, such as `variant=\"light\"`. However, we also want to keep the layout persistent in the React tree, to avoid having to remount it every time a user navigate between pages.\n\nHistorically, projects overlook the need of multiple layouts or the ability to change layout props between pages. They start off with a simple layout and only later they handle this need, often with poor and non-scalable solutions.\n\nThis library solves the need for multi-layouts and changing layout props dynamically in a consistent and reusable way.\n\n## Usage\n\nSetup `\u003cLayoutTree\u003e` in your `pages/_app.js` component:\n\n```js\nimport React from 'react';\nimport { LayoutTree } from '@moxy/next-layout';\n\nconst App = ({ Component, pageProps }) =\u003e (\n    \u003cLayoutTree\n        Component={ Component }\n        pageProps={ pageProps } /\u003e\n);\n\nexport default App;\n```\n\n...and then use `withLayout` in your page components, e.g.: in `pages/about.js`:\n\n```js\nimport React from 'react';\nimport { withLayout } from '@moxy/next-layout';\nimport { PrimaryLayout } from '../components';\nimport styles from './about.module.css';\n\nconst About = () =\u003e (\n    \u003cdiv className={ styles.about }\u003e\n        \u003ch1\u003eAbout\u003c/h1\u003e\n    \u003c/div\u003e\n);\n\nexport default withLayout(\u003cPrimaryLayout variant=\"light\" /\u003e)(About);\n```\n\nℹ️ The `PrimaryLayout` component will receive the page to be rendered as the `children` prop.\n\n### Nested layouts\n\nNested layouts are as easy as nesting them in the `withLayout`. Let's say that you have two account pages, `pages/account/profile.js` and `pages/account/settings.js`, and you want them to be wrapped by an `AccountLayout`. You would define the pages like so:\n\n```js\n// pages/account/profile.js\nimport React from 'react';\nimport { withLayout } from '@moxy/next-layout';\nimport { PrimaryLayout, AccountLayout } from '../components';\nimport styles from './.account-profile.module.css';\n\nconst AccountProfile = () =\u003e (\n    \u003cdiv className={ styles.accountProfile }\u003e\n        \u003ch1\u003eAccount Profile\u003c/h1\u003e\n    \u003c/div\u003e\n);\n\nexport default withLayout(\n    \u003cPrimaryLayout\u003e\n        \u003cAccountLayout /\u003e\n    \u003cPrimaryLayout /\u003e\n)(AccountProfile);\n```\n\n```js\n// pages/account/settings.js\nimport React from 'react';\nimport { withLayout } from '@moxy/next-layout';\nimport { PrimaryLayout, AccountLayout } from '../components';\nimport styles from './account-settings.module.css';\n\nconst AccountSettings = () =\u003e (\n    \u003cdiv className={ styles.accountSettings }\u003e\n        \u003ch1\u003eAccount Settings\u003c/h1\u003e\n    \u003c/div\u003e\n);\n\nexport default withLayout(\n    \u003cPrimaryLayout\u003e\n        \u003cAccountLayout /\u003e\n    \u003cPrimaryLayout /\u003e\n)(AccountSettings);\n```\n\nℹ️ The `PrimaryLayout` component will receive `AccountLayout` as a children, which in turn will receive the page as children too.\n\nℹ️ You could create a `withAccountLayout` HOC to avoid repeating the layout tree in every account page.\n\n⚠️ The layout tree specified in `withLayout` must be a unary tree, that is, a tree where nodes just have one child.\n\n## API\n\n`@moxy/next-layout` exposes a `\u003cLayoutTree\u003e` component and a `withLayout` HOC to be used in pages.\n\n### \u0026lt;LayoutTree\u0026gt;\n\nA component that infers the layout tree based on what the active page specifies. It keeps the layout persistent between page transitions whenever possible (e.g.: when the layout is the same).\n\nHere's the list of props it supports:\n\n#### Component\n\nType: `ReactElementType`\n\nThe page component, which maps to your App `Component` prop.\n\n#### pageProps\n\nType: `object`\n\nThe page component props, which maps to your App `pageProps` prop.\n\n#### pageKey?\n\nType: `string`\n\nThe page key used to uniquely identify this page. Useful for dynamic routes, where the `Component` is the same, but you still want the page to be re-mounted. For such cases, you may use `router.asPath.replace(/\\?.+/, '')`.\n\n#### defaultLayout\n\nType: `ReactElement`   \n\nThe default layout tree to be used when a child page doesn't explicitly sets one.\n\n```js\n// pages/_app.js\nimport React from 'react';\nimport { LayoutTree } from '@moxy/next-layout';\nimport { PrimaryLayout } from '../components';\n\nconst App = ({ Component, pageProps }) =\u003e (\n    \u003cLayoutTree\n        Component={ Component }\n        pageProps={ pageProps }\n        defaultLayout={ \u003cPrimaryLayout /\u003e } /\u003e\n);\n\nexport default App;\n```\n\n#### children\n\nType: `function`\n\nA [render prop](https://reactjs.org/docs/render-props.html) to override the default render behavior, which just regularly renders the tree.\n\nIts signature is `(tree) =\u003e \u003cReactElement\u003e`, where: `tree` is the React's tree composed by layout elements and a leaf page element.\n\nThis might be useful if you want to add animations between page transitions.\n\n### withLayout(mapLayoutStateToLayoutTree?, initialLayoutState?)(Page)\n\nSets up a `Page` component with the ability to specify which layout tree to use. Moreover, it injects a `setLayoutState` prop so that you may dynamically update the layout tree.\n\n#### mapLayoutStateToLayoutTree\n\nType: `ReactElement` or `function`\n\nIn simple cases, you may define a \"static\" layout tree, like so:\n\n```js\nexport default withLayout(\u003cPrimaryLayout variant=\"light\" /\u003e)(Home);\n```\n\nHowever, you might have external props, component state or other mutations influencing the layout tree. In those cases, you may pass a function that maps **layout state** into a tree, with the following signature: `(layoutState) =\u003e \u003cReactElement\u003e`. Here's an example:\n\n```js\nconst mapLayoutStateToLayoutTree = ({ variant }) =\u003e \u003cPrimaryLayout variant={ variant } /\u003e;\n\nexport default withLayout(mapLayoutStateToLayoutTree, { variant: 'light' })(Home);\n```\n\nThe function is run initially and every time the *layout state* changes.\n\n#### initialLayoutState\n\nType: `object` or `function`\n\nThe initial **layout state** to be passed to `mapLayoutStateToLayoutTree`. If your initial *layout state* depends on the props you receive, you may pass a function with the following signature: `(props) =\u003e \u003cobject\u003e`.\n\n#### Page\n\nType: `ReactElementType`\n\nThe page component to wrap.\n\n#### Injected setLayoutState\n\nType: `function`\n\nAllows dynamic changes to the layout state. Has the following signature: `(newState | updater?)`.\n\nThe behavior of `setLayoutState` is exactly the same as [`setState`](https://reactjs.org/docs/react-component.html#setstate) of class components: it merges properties and it supports both an object or an updater function.\n\n```js\n// pages/about.js\nimport React, { useCallback } from 'react';\nimport { withLayout } from '@moxy/next-layout';\nimport { PrimaryLayout } from '../components';\n\nimport styles from './about.module.css';\n\nconst About = ({ setLayoutState }) =\u003e {\n    const handleSetToDark = useCallback(() =\u003e {\n        setLayoutState({ variant: 'dark' });\n        // ..or setLayoutState((layoutState) =\u003e ({ variant: 'dark' }));\n    }, [setLayoutState]);\n\n    return (\n        \u003cdiv className={ styles.about }\u003e\n            \u003ch1\u003eAbout\u003c/h1\u003e\n            \u003cbutton onClick={ handleSetToDark }\u003eEnable dark mode\u003c/button\u003e\n        \u003c/div\u003e\n    );\n};\n\nconst mapLayoutStateToLayoutTree = ({ variant }) =\u003e \u003cPrimaryLayout variant={ variant } /\u003e;\n\nexport default withLayout(mapLayoutStateToLayoutTree, { variant: 'light' })(About);\n```\n\n## Tests\n\n```sh\n$ npm test\n$ npm test -- --watch # during development\n```\n\n## License\n\nReleased under the [MIT License](https://www.opensource.org/licenses/mit-license.php).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoxystudio%2Fnext-layout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoxystudio%2Fnext-layout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoxystudio%2Fnext-layout/lists"}