This repository has been archived by the owner on Jun 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Components): New BackButton component
- Loading branch information
1 parent
5dfb0ec
commit 57a84b5
Showing
4 changed files
with
156 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import React, {createContext, memo, useEffect, useRef} from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export const BackButtonContext = createContext(false); | ||
|
||
function Provider({children, pathname}) { | ||
const hasHistoryRef = useRef(false); | ||
|
||
useEffect(() => { | ||
// We're using a ref so that we don't trigger a rerender, | ||
// as the new value should only apply on the next render, | ||
// i.e. the next time `pathname` has changed | ||
hasHistoryRef.current = true; | ||
}, [pathname]); | ||
|
||
return ( | ||
<BackButtonContext.Provider value={hasHistoryRef.current}> | ||
{children} | ||
</BackButtonContext.Provider> | ||
); | ||
} | ||
|
||
// Memoise the component to make sure the component is only ever | ||
// re-rendered when the `pathname` prop changes | ||
const BackButtonProvider = memo( | ||
Provider, | ||
(prevProps, nextProps) => prevProps.pathname === nextProps.pathname | ||
); | ||
|
||
Provider.propTypes = { | ||
pathname: PropTypes.string.isRequired, | ||
}; | ||
|
||
export default BackButtonProvider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
--- | ||
name: BackButton | ||
menu: Components | ||
--- | ||
|
||
import {Playground, Props} from 'docz'; | ||
import InlineList from '../InlineList'; | ||
import BackButton, {BackButtonProvider} from './'; | ||
import Button from '../Button'; | ||
import TextLink from '../TextLink'; | ||
|
||
# BackButton | ||
|
||
A component for handling backlinks for navigation in apps. | ||
|
||
## The problem | ||
|
||
Hard-coding back links in an app where pages may have different entry points can quickly lead to a bad user experience. Users expect a link or button labelled "Back" to take them back to the page they came from – just like their browser's back button. | ||
|
||
So, one might think, why not just trigger the browser's back button using `history.back`? We need back buttons to have a sensible fallback for when there's no browser history yet, for example after a link has been opened in a new window, or when a page was reached via a link in an email. | ||
|
||
This is where this component comes in. Wrap your app with the `BackButtonProvider` component and pass it the current pathname. In react-router, you can get it from the `useLocation` hook: | ||
|
||
```js | ||
import {useLocation} from 'react-router'; | ||
import {BackButtonProvider} from 'base5-ui/BackButton'; | ||
|
||
function YourApp({children}) { | ||
const {pathname} = useLocation(); | ||
|
||
return ( | ||
<BackButtonProvider pathname={pathname}>{children}</BackButtonProvider> | ||
); | ||
} | ||
``` | ||
|
||
`BackButtonProvider` tracks whether the user has navigated in the app. It then provides this information to the `BackButton` component via context. `BackButton` then either renders a `<button>` element, or the fallback link specified by you. | ||
|
||
For visual customisation, you can pass the component to render using the `baseComponent` prop. Please make sure that this component has support for changing the underlying rendered element using the `as` prop (this is the case for components built using styled-components by default). | ||
|
||
## Examples | ||
|
||
<Playground> | ||
<BackButtonProvider pathname="/current/url"> | ||
<BackButton | ||
baseComponent={Button} | ||
as="a" | ||
href="/fallback-link" | ||
icon="arrow" | ||
> | ||
Back button | ||
</BackButton> | ||
<br /> | ||
<br /> | ||
<BackButton baseComponent={TextLink} as="a" href="/fallback-link"> | ||
Backlink | ||
</BackButton> | ||
</BackButtonProvider> | ||
</Playground> | ||
|
||
## Props | ||
|
||
<Props of={BackButton} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import useBackButton from './useBackButton'; | ||
import BackButtonProvider from './BackButtonProvider'; | ||
|
||
import ButtonCore from '../ButtonCore'; | ||
|
||
function BackButton({ | ||
baseComponent: Component, | ||
as, | ||
onClick, | ||
onGoBack, | ||
...otherProps | ||
}) { | ||
const hasHistory = useBackButton(); | ||
|
||
function goBack(e) { | ||
e.preventDefault; | ||
onGoBack ? onGoBack() : history.back(); | ||
if (onClick) onClick(e); | ||
} | ||
|
||
if (hasHistory) { | ||
return <Component as="button" onClick={goBack} {...otherProps} />; | ||
} | ||
return <Component as={as} onClick={onClick} {...otherProps} />; | ||
} | ||
|
||
BackButton.defaultProps = { | ||
baseComponent: ButtonCore, | ||
}; | ||
|
||
BackButton.propTypes = { | ||
/** | ||
* The base component to use, i.e. Button or TextLink. | ||
* This component **must** support the `as` prop for | ||
* changing the underlying rendered element | ||
*/ | ||
baseComponent: PropTypes.elementType, | ||
/** | ||
* By default, `window.history.back` is triggered when | ||
* a history is available. Use this prop to provide | ||
* another function, e.g. react-router's `history.goBack` | ||
*/ | ||
onGoBack: PropTypes.func, | ||
}; | ||
|
||
export {BackButtonProvider}; | ||
|
||
// @component | ||
export default BackButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {useContext} from 'react'; | ||
import {BackButtonContext} from './BackButtonProvider'; | ||
|
||
function useBackButton() { | ||
return useContext(BackButtonContext); | ||
} | ||
|
||
export default useBackButton; |