Skip to content

Commit

Permalink
Merge pull request #1106 from cozy/page-layout
Browse files Browse the repository at this point in the history
feat: Page layout
  • Loading branch information
ptbrowne authored Jul 18, 2019
2 parents b586afb + 7d3e8e0 commit b0d0b6b
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 19 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@
"preact-portal": "1.1.3",
"pretty": "2.0.0",
"prop-types": "15.7.2",
"react": "16.6.3",
"react-dom": "16.6.3",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-redux": "5.1.1",
"react-styleguidist": "7.3.11",
"react-test-renderer": "16.6.3",
Expand Down Expand Up @@ -134,8 +134,8 @@
"piwik-react-router": "^0.8.2",
"preact": "^8.3.1",
"preact-portal": "^1.1.3",
"react": "^16.6.3",
"react-dom": "^16.5.2"
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"eslintConfig": {
"extends": [
Expand Down
13 changes: 13 additions & 0 deletions react/Page/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
The Page components enables to make layout that react well to keyboard appearing/disappearing.

In the example below, the Button will appear at the bottom of the screen even if the PageContent
does not takes all the space (the content grows to fill all the page). When the keyboard is
shown, the Page real estate shrink and the Button will try to appear above the keyboard if it
has enough space.

```jsx static
<PageLayout>
<PageContent>Hello world !</PageContent>
<PageFooter><Button>Click me !</Button></PageFooter>
</PageLayout>
```
111 changes: 111 additions & 0 deletions react/Page/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Layout components that know how to handle the keyboard.
*
* Work when the webview does not resize when the keyboard appears.
*
* https://github.com/ionic-team/cordova-plugin-ionic-keyboard
* <preference name="KeyboardResize" value="false" />
*/

import React from 'react'
import { useKeyboardInfo } from './keyboard'
import styles from './styles.styl'
import withBreakpoints from '../helpers/withBreakpoints'

const TOP_BAR_HEIGHT = 48
const BOTTOM_BAR_HEIGHT = 48

/**
* Returns the min-height CSS property for the Page
*
* The goal is for the Page to fill all the screen that is visible to the user.
* Since the keyboard appearing does not have any effect on the layout of the
* page (), we have to remove its height from the available space when it appears.
*
* Also handles fixed bars space:
*
* - topBar height is removed from the real estate available.
* - bottomBar height is removed from the real estate available, unless the
* keyboard is visible : since the bottom bar is expected to be hidden
* when an input is focused (to prevent iOS flickers), it does not take space
* when the keyboard is visible.
*/
const contentHeight = ({
topBarHeight = TOP_BAR_HEIGHT,
bottomBarHeight = BOTTOM_BAR_HEIGHT,
extraHeight = 0,
keyboardState,
keyboardHeight
} = {}) => {
const removedSpace =
topBarHeight +
(keyboardState === 'showing' ? 0 : bottomBarHeight) +
// Surprisingly, vh changes when keyboard appears and 1 keyboard is added
// to the vh, this is why instead of adding 1 keyboardHeight, we add 2.
(keyboardState === 'showing' ? 2 * keyboardHeight : 0) +
extraHeight
return `calc(100vh ${removedSpace > 0 ? '-' : '+'} ${Math.abs(
removedSpace
)}px)`
}

/**
* Empty container, that has the same height as the keyboard when it is opened,
* used as a "cushion" upon which the PageFooter rests when the keyboard is opened.
*/
const KeyboardSpace = () => {
const { keyboardState, keyboardHeight } = useKeyboardInfo()
return (
<div
style={{
height: keyboardState === 'showing' ? keyboardHeight : 0
}}
>
{' '}
</div>
)
}

export const MobilePageLayout = ({ children, extraHeight = 0 }) => {
const { keyboardState, keyboardHeight } = useKeyboardInfo()
const minHeight = contentHeight({
keyboardState,
keyboardHeight,
extraHeight
})
return (
<>
<div
className={styles.PageLayout}
style={{
minHeight
}}
>
{children}
</div>
<KeyboardSpace />
</>
)
}

/**
* - On mobile, wraps into MobilePageLayout.
* - On desktop, wraps into a simple div.
*/
export const PageLayout = React.memo(
withBreakpoints()(({ breakpoints: { isMobile }, ...props }) => {
return isMobile ? (
<MobilePageLayout {...props} />
) : (
<div>{props.children}</div>
)
})
)

export const PageContent = React.memo(({ children }) => (
<div className={styles.PageContent}>{children}</div>
))

export const PageFooter = ({ children }) => (
<div className={styles.PageFooter}>{children}</div>
)
104 changes: 104 additions & 0 deletions react/Page/keyboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Hooks to access keyboard properties.
*
* Works with events sent by cordova-plugin-ionic-keyboard.
*
* https://github.com/ionic-team/cordova-plugin-ionic-keyboard
*/

import { useEffect, useState } from 'react'

/**
* Helper hook that makes writing hooks for keyboard easier.
*
* With this hook, you can provide an event handler for a particular
* keyboard event and its lifecycle will be handled for you (attach events
* on component mount, removes them on component unmount).
*/
const useKeyboard = ({
onKeyboardWillShow,
onKeyboardWillHide,
onKeyboardHeightWillChange,
onKeyboardDidHide,
onKeyboardDidShow
}) => {
useEffect(() => {
if (!window.Keyboard) {
console.warn(
'window.Keyboard is falsy. The `useKeyboard` hook cannot listen to keyboard events if the `cordova-plugin-ionic-keyboard` plugin is not installed.'
)
}
onKeyboardWillShow &&
window.addEventListener('keyboardWillShow', onKeyboardWillShow)
onKeyboardWillHide &&
window.addEventListener('keyboardWillHide', onKeyboardWillHide)
onKeyboardHeightWillChange &&
window.addEventListener(
'keyboardHeightWillChange',
onKeyboardHeightWillChange
)
onKeyboardDidHide &&
window.addEventListener('keyboardDidHide', onKeyboardDidHide)
onKeyboardDidShow &&
window.addEventListener('keyboardDidShow', onKeyboardDidShow)
return () => {
onKeyboardWillShow &&
window.removeEventListener('keyboardWillShow', onKeyboardWillShow)
onKeyboardWillHide &&
window.removeEventListener('keyboardWillHide', onKeyboardWillHide)
onKeyboardHeightWillChange &&
window.removeEventListener(
'keyboardHeightWillChange',
onKeyboardHeightWillChange
)
onKeyboardDidHide &&
window.removeEventListener('keyboardDidHide', onKeyboardDidHide)
onKeyboardDidShow &&
window.removeEventListener('keyboardDidShow', onKeyboardDidShow)
}
}, [
onKeyboardWillShow,
onKeyboardWillHide,
onKeyboardHeightWillChange,
onKeyboardDidHide,
onKeyboardDidShow
])
}

/**
* Provides keyboardHeight and keyboardState
*
* keyboardState can be
*
* - will-hide
* - will-show
* - hidden
* - showing
*
* More information on the keyboard plugin page:
* https://github.com/ionic-team/cordova-plugin-ionic-keyboard
*/
const useKeyboardInfo = () => {
const [keyboardHeight, setKeyboardHeight] = useState(0)
const [keyboardState, setKeyboardState] = useState('hidden')
useKeyboard({
onKeyboardWillHide: () => {
setKeyboardState('will-hide')
},
onKeyboardWillShow: () => {
setKeyboardState('will-show')
},
onKeyboardDidHide: () => {
setKeyboardState('hidden')
},
onKeyboardDidShow: ev => {
if (ev.keyboardHeight) {
setKeyboardHeight(ev.keyboardHeight)
}
setKeyboardState('showing')
}
})
return { keyboardHeight, keyboardState }
}

export { useKeyboard, useKeyboardInfo }
9 changes: 9 additions & 0 deletions react/Page/styles.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.PageFooter
flex-grow 0

.PageContent
flex-grow 1

.PageLayout
display flex
flex-direction column
1 change: 1 addition & 0 deletions react/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ export { default as Card } from './Card'
export { default as InlineCard } from './InlineCard'
export { default as PercentageLine } from './PercentageLine'
export { default as InputGroup } from './InputGroup'
export { PageFooter, PageContent, PageLayout } from './Page'
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11767,15 +11767,15 @@ [email protected]:
node-dir "^0.1.10"
recast "^0.13.0"

react-dom@16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0"
integrity sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==
react-dom@16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f"
integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.11.2"
scheduler "^0.13.6"

react-error-overlay@^4.0.1:
version "4.0.1"
Expand Down Expand Up @@ -12000,16 +12000,6 @@ react-transition-group@^2.2.1:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"

[email protected]:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c"
integrity sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.11.2"

[email protected]:
version "16.7.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381"
Expand All @@ -12020,6 +12010,16 @@ [email protected]:
prop-types "^15.6.2"
scheduler "^0.12.0"

[email protected]:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.13.6"

read-cache@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
Expand Down

0 comments on commit b0d0b6b

Please sign in to comment.