Skip to content

Commit ef27429

Browse files
authored
feat: initial implementation (#1)
1 parent 5440139 commit ef27429

19 files changed

+12670
-1
lines changed

.commitlintrc.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": [
3+
"@commitlint/config-conventional"
4+
]
5+
}

.editorconfig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 4
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[package.json]
13+
indent_size = 2
14+
15+
[{*.md,*.snap}]
16+
trim_trailing_whitespace = false

.eslintrc.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"root": true,
3+
"extends": [
4+
"@moxy/eslint-config/es10",
5+
"@moxy/eslint-config/addons/browser",
6+
"@moxy/eslint-config/addons/node",
7+
"@moxy/eslint-config/addons/react",
8+
"@moxy/eslint-config/addons/babel-parser",
9+
"@moxy/eslint-config/addons/es-modules",
10+
"@moxy/eslint-config/addons/jest"
11+
]
12+
}

.github/workflows/node-ci.yml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Node CI
2+
3+
on: [push]
4+
5+
jobs:
6+
7+
check:
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
node: ['12', '13']
12+
name: "[v${{ matrix.node }}] prepare"
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v1
17+
18+
- name: Install dependencies
19+
run: |
20+
npm ci
21+
22+
- name: Run lint & tests
23+
env:
24+
CI: 1
25+
run: |
26+
npm run lint
27+
npm t
28+
29+
- name: Submit coverage
30+
uses: codecov/codecov-action@v1
31+
with:
32+
token: ${{ secrets.CODECOV_TOKEN }}
33+
fail_ci_if_error: true

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
npm-debug.log*
3+
coverage
4+
lib/
5+
es/

.huskyrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"hooks": {
3+
"pre-commit": "lint-staged",
4+
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
5+
}
6+
}

.lintstagedrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"*.js": "eslint"
3+
}

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 Made With MOXY Lda <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+220-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,221 @@
11
# next-layout
2-
Let your Next.js pages choose which layout to use, while keeping layouts persistent whenever possible
2+
3+
[![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]
4+
5+
[npm-url]:https://npmjs.org/package/@moxy/next-layout
6+
[downloads-image]:https://img.shields.io/npm/dm/@moxy/next-layout.svg
7+
[npm-image]:https://img.shields.io/npm/v/@moxy/next-layout.svg
8+
[build-status-url]:https://github.com/moxystudio/next-layout/actions
9+
[build-status-image]:https://img.shields.io/github/workflow/status/moxystudio/next-layout/Node%20CI/master
10+
[codecov-url]:https://codecov.io/gh/moxystudio/next-layout
11+
[codecov-image]:https://img.shields.io/codecov/c/github/moxystudio/next-layout/master.svg
12+
[david-dm-url]:https://david-dm.org/moxystudio/next-layout
13+
[david-dm-image]:https://img.shields.io/david/moxystudio/next-layout.svg
14+
[david-dm-dev-url]:https://david-dm.org/moxystudio/next-layout?type=dev
15+
[david-dm-dev-image]:https://img.shields.io/david/dev/moxystudio/next-layout.svg
16+
17+
Add persistent layouts to your Next.js projects in a declarative way.
18+
19+
## Installation
20+
21+
```sh
22+
$ npm install @moxy/next-layout
23+
```
24+
25+
This 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.
26+
27+
## Motivation
28+
29+
Next.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 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.
30+
31+
Historically, 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.
32+
33+
This library solves the need for multi-layouts and changing layout props dynamically in a consistent and reusable way.
34+
35+
## Usage
36+
37+
Setup `<LayoutManager>` in your `pages/_app.js` component:
38+
39+
```js
40+
import React from 'react';
41+
import { LayoutManager } from '@moxy/next-layout';
42+
43+
const App = ({ Component, pageProps }) => (
44+
<LayoutManager
45+
Component={ Component }
46+
pageProps={ pageProps } />
47+
);
48+
49+
export default App;
50+
```
51+
52+
...and then use `withLayout` in your page components, e.g.: in `pages/about.js`:
53+
54+
```js
55+
import React from 'react';
56+
import { withLayout } from '@moxy/next-layout';
57+
import { PrimaryLayout } from '../components';
58+
import styles from './about.module.css';
59+
60+
const About = () => (
61+
<div className={ styles.about }>
62+
<h1>About</h1>
63+
</div>
64+
);
65+
66+
export default withLayout(<PrimaryLayout variant="light" />)(About);
67+
```
68+
69+
ℹ️ Layouts will receive the page to be rendered as the `children` prop.
70+
71+
## API
72+
73+
`@moxy/next-layout` exposes a `<LayoutManager>` component and a `withLayout` to be used in pages.
74+
75+
### &lt;LayoutManager&gt;
76+
77+
A component that manages the current layout to be used 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).
78+
79+
Here's the list of props it supports:
80+
81+
#### Component
82+
83+
Type: `ReactElementType`
84+
85+
The page component, which maps to your App `Component` prop.
86+
87+
#### pageProps
88+
89+
Type: `object`
90+
91+
The page component props, which maps to your App `pageProps` prop.
92+
93+
#### defaultLayout
94+
95+
Type: `ReactElement`
96+
97+
The default layout to be used when a child page doesn't explicitly sets one.
98+
99+
```js
100+
// pages/_app.js
101+
import React from 'react';
102+
import { LayoutManager } from '@moxy/next-layout';
103+
import { PrimaryLayout } from '../components';
104+
105+
const App = ({ Component, pageProps }) => (
106+
<LayoutManager
107+
Component={ Component }
108+
pageProps={ pageProps }
109+
defaultLayout={ <PrimaryLayout /> } />
110+
);
111+
112+
export default App;
113+
```
114+
115+
#### children
116+
117+
Type: `function`
118+
119+
A [render prop](https://reactjs.org/docs/render-props.html) to override the default render behavior.
120+
121+
Its signature is `({ Layout, layoutProps, layoutKey, Component, pageProps, pageKey }) => <ReactElement>`, where:
122+
123+
- `Layout` is the layout React component that should be rendered
124+
- `layoutProps` is the props that should be passed to the layout React component
125+
- `layoutKey` is a unique string for the layout to be used as `key`
126+
- `Component` is the page React component that should be rendered
127+
- `pageProps` is the props that should be passed to the page React component, and already includes `setLayoutProps` if the page was wrapped with [`withLayout`](#withlayoutlayoutpage)
128+
- `pageKey` is a unique string for the page to be used as `key`
129+
130+
Passing a custom `children` render prop is useful to add layout and page transitions. Here's an example that uses [`react-transition-group`](https://reactcommunity.org/react-transition-group/) to apply a simple fade transition between layouts and pages:
131+
132+
```js
133+
// pages/_app.js
134+
import React from 'react';
135+
import { LayoutManager } from '@moxy/next-layout';
136+
import { TransitionGroup, CSSTransition } from 'react-transition-group';
137+
import { PrimaryLayout } from '../components';
138+
139+
const App = ({ Component, pageProps }) => (
140+
<LayoutManager
141+
Component={ Component }
142+
pageProps={ pageProps }
143+
defaultLayout={ <PrimaryLayout /> }>
144+
{ ({ Layout, layoutProps, layoutKey, Component, pageProps, pageKey }) => (
145+
<TransitionGroup>
146+
<CSSTransition key={ layoutKey } classNames="fade">
147+
<Layout { ...layoutProps }>
148+
<TransitionGroup>
149+
<CSSTransition key={ pageKey } classNames="fade">
150+
<Component { ...pageProps } />
151+
</CSSTransition>
152+
</TransitionGroup>
153+
</Layout>
154+
</CSSTransition>
155+
</TransitionGroup>
156+
) }
157+
</LayoutManager>
158+
);
159+
```
160+
161+
### withLayout(layout?)(Page)
162+
163+
Sets up a `Page` component with the ability to select which `layout` to use. Moreover, it injects a `setLayoutProps` prop so that you may dynamically update the layout props.
164+
165+
#### layout
166+
167+
Type: `ReactElement` or `function`
168+
169+
The layout to use for the `Page`. Can either be a `ReactElement` or a function that returns it.
170+
171+
The function form is useful when page props affects layout props. It has the following signature: `(ownProps) => <ReactElement>`. Please note that the function only runs once to determine the layout and its initial props.
172+
173+
#### Page
174+
175+
Type: `ReactElementType`
176+
177+
The page component to wrap.
178+
179+
#### Injected setLayoutProps
180+
181+
Type: `function`
182+
183+
Allows to dynamically change the layout props. Has the following signature: `(updater | stateChange, callback?)`.
184+
185+
The behavior of `setLayoutProps` is exactly the same as [`setState`](https://reactjs.org/docs/react-component.html#setstate) of class components, supporting both an object or an updater function.
186+
187+
```js
188+
// pages/about.js
189+
import React, { useCallback } from 'react';
190+
import { withLayout } from '@moxy/next-layout';
191+
import { PrimaryLayout } from '../components';
192+
193+
import styles from './about.module.css';
194+
195+
const About = ({ setLayoutProps }) => {
196+
const handleSetToDark = useCallback(() => {
197+
setLayoutProps({ variant="dark" });
198+
// ..or setLayoutProps((layoutProps) => ({ variant="dark" }));
199+
}, [setLayoutProps]);
200+
201+
return (
202+
<div className={ styles.about }>
203+
<h1>About</h1>
204+
<button onClick={ handleSetToDark }>Enable dark mode</button>
205+
</div>
206+
);
207+
};
208+
209+
export default withLayout(<PrimaryLayout variant="light" />)(About);
210+
```
211+
212+
## Tests
213+
214+
```sh
215+
$ npm test
216+
$ npm test -- --watch # during development
217+
```
218+
219+
## License
220+
221+
Released under the [MIT License](https://www.opensource.org/licenses/mit-license.php).

babel.config.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = (api) => {
2+
api.cache(true);
3+
4+
return {
5+
ignore: process.env.NODE_ENV === 'test' ? [] : ['**/*.test.js', '**/__snapshots__'],
6+
presets: [
7+
['@moxy/babel-preset/lib', { react: true }],
8+
],
9+
};
10+
};

jest.config.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { compose, baseConfig, withRTL } = require('@moxy/jest-config');
2+
3+
module.exports = compose([
4+
baseConfig,
5+
withRTL,
6+
]);
7+

0 commit comments

Comments
 (0)