From 40451463645a8e8490a7f0f7f7a1a0748b1fa2f5 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Thu, 16 Feb 2017 17:15:27 +0200 Subject: [PATCH] Add support for activeClassName for the NamedLink component --- .../__snapshots__/FilterPanel.test.js.snap | 8 +- .../__snapshots__/ListingCard.test.js.snap | 4 +- .../ListingCardSmall.test.js.snap | 4 +- .../__snapshots__/MapPanel.test.js.snap | 8 +- src/components/NamedLink/NamedLink.js | 76 +++++++++++++++---- src/components/NamedLink/NamedLink.test.js | 38 +++++++++- .../OrderDetailsPanel.test.js.snap | 4 +- .../SearchResultsPanel.test.js.snap | 8 +- .../__snapshots__/CheckoutPage.test.js.snap | 4 +- .../__snapshots__/ListingPage.test.js.snap | 4 +- .../__snapshots__/NotFoundPage.test.js.snap | 4 +- .../__snapshots__/OrderPage.test.js.snap | 8 +- src/containers/Topbar/Topbar.js | 14 +++- 13 files changed, 137 insertions(+), 47 deletions(-) diff --git a/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap b/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap index 04a6adea..f25c5d6d 100644 --- a/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap +++ b/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap @@ -3,13 +3,13 @@ exports[`FilterPanel matches snapshot 1`] = `

Filters

- See studios - - + X - + `; diff --git a/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap b/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap index b8bb18f0..6290a4ac 100644 --- a/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap +++ b/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap @@ -9,7 +9,7 @@ exports[`ListingCard matches snapshot 1`] = `
- Banyan Studios - +
55€ / day
diff --git a/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap b/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap index 65fb4adc..18698074 100644 --- a/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap +++ b/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap @@ -8,7 +8,7 @@ exports[`ListingCardSmall matches snapshot 1`] = `
- Banyan Studios - +
( diff --git a/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap b/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap index 336fb552..96d8ea82 100644 --- a/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap +++ b/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap @@ -4,13 +4,13 @@ exports[`MapPanel matches snapshot 1`] = ` Map
- Filters - - + X - +
`; diff --git a/src/components/NamedLink/NamedLink.js b/src/components/NamedLink/NamedLink.js index 90f0816e..329699ab 100644 --- a/src/components/NamedLink/NamedLink.js +++ b/src/components/NamedLink/NamedLink.js @@ -1,30 +1,80 @@ /** * This component wraps React-Router's Link by providing name-based routing. - * (Helps to narrow down the scope of possible format changes to routes.) + * + * The `name` prop should match a route in the flattened + * routesConfiguration passed in context by the RoutesProvider + * component. The `params` props is the route params for the route + * path of the given route name. + * + * The `to` prop is an object with the same shape as Link requires, + * but without `pathname` that will be generated from the given route + * name. + * + * Some additional props can be passed for the element like + * `className` and `style`. + * + * The component can also be given the `activeClassName` prop that + * will be added to the element className if the current URL matches + * the one in the generated pathname of the link. */ import React, { PropTypes } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, withRouter } from 'react-router-dom'; +import classNames from 'classnames'; import { pathByRouteName, withFlattenedRoutes } from '../../util/routes'; import * as propTypes from '../../util/propTypes'; -const NamedLink = props => { - const { name, search, hash, state, params, flattenedRoutes, ...rest } = props; +export const NamedLinkComponent = props => { + const { name, params, flattenedRoutes } = props; + + // Link props + const { to, children } = props; + const pathname = pathByRouteName(name, flattenedRoutes, params); - const locationDescriptor = { pathname, search, hash, state }; - return ; + const { match } = props; + const active = match.url && match.url === pathname; + + // element props + const { className, style, activeClassName } = props; + const aElemProps = { className: classNames(className, { [activeClassName]: active }), style }; + + return {children}; }; -const { arrayOf, object, string } = PropTypes; +const { arrayOf, object, string, shape, any } = PropTypes; -NamedLink.defaultProps = { search: '', hash: '', state: {}, params: {} }; +NamedLinkComponent.defaultProps = { + params: {}, + to: {}, + children: null, + className: '', + style: {}, + activeClassName: 'NamedLink_active', + match: {}, +}; -NamedLink.propTypes = { +// This ensures a nice display name in snapshots etc. +NamedLinkComponent.displayName = 'NamedLink'; + +NamedLinkComponent.propTypes = { + // name of the route in routesConfiguration name: string.isRequired, - search: string, - hash: string, - state: object, + // params object for the named route params: object, + // Link component props + to: shape({ search: string, hash: string, state: object }), + children: any, + // Note: The Link `replace` prop conflicts with the replace function + // from withRouter. If the replace boolean is required for the Link + // component, add it here with another name. + + // generic props for the underlying element + className: string, + style: object, + activeClassName: string, + // from withFlattenedRoutes flattenedRoutes: arrayOf(propTypes.route).isRequired, + // from withRouter + match: object, }; -export default withFlattenedRoutes(NamedLink); +export default withFlattenedRoutes(withRouter(NamedLinkComponent)); diff --git a/src/components/NamedLink/NamedLink.test.js b/src/components/NamedLink/NamedLink.test.js index 14f6443f..03a2f621 100644 --- a/src/components/NamedLink/NamedLink.test.js +++ b/src/components/NamedLink/NamedLink.test.js @@ -1,7 +1,41 @@ import React from 'react'; import { RoutesProvider } from '../index'; -import { renderDeep } from '../../util/test-helpers'; -import NamedLink from './NamedLink'; +import { renderShallow, renderDeep } from '../../util/test-helpers'; +import NamedLink, { NamedLinkComponent } from './NamedLink'; + +describe('NamedLinkComponent', () => { + it('should mark the link as active if the current URL matches', () => { + const routesConf = [ + { path: '/a', name: 'APage', component: () => null }, + { path: '/b', name: 'BPage', component: () => null }, + ]; + const activeClassName = 'my-active-class'; + const aProps = { + name: 'APage', + activeClassName, + flattenedRoutes: routesConf, + match: { url: '/a' }, + }; + const bProps = { + name: 'BPage', + activeClassName, + flattenedRoutes: routesConf, + match: { url: '/a' }, + }; + const tree = renderDeep( +
+ link to a + link to b +
, + ); + const aLink = tree.children[0]; + const bLink = tree.children[1]; + expect(aLink.type).toEqual('a'); + expect(bLink.type).toEqual('a'); + expect(aLink.props.className).toEqual(activeClassName); + expect(bLink.props.className).toEqual(''); + }); +}); describe('NamedLink', () => { it('should contain correct link', () => { diff --git a/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap b/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap index 39d7a979..46780dd4 100644 --- a/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap +++ b/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap @@ -28,7 +28,7 @@ exports[`OrderDetailsPanel matches snapshot 1`] = `

Cancel booking

- You have a new message! - +
`; diff --git a/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap b/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap index 5f62568c..63c3d2b3 100644 --- a/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap +++ b/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap @@ -2,14 +2,14 @@ exports[`SearchResultsPanel matches snapshot 1`] = `
- Filters - - + Map - +
`; diff --git a/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap b/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap index ecf1aacc..288c49c4 100644 --- a/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap +++ b/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap @@ -19,7 +19,7 @@ exports[`CheckoutPage matches snapshot 1`] = `

By confirming I accept the booking terms and conditions.

- Confirm & Pay - + `; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index f42598fc..b621a18c 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -123,7 +123,7 @@ exports[`ListingPage matches snapshot 1`] = `
Map
- Book Banyan Studios - + `; diff --git a/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap b/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap index 7b0aabf7..89266184 100644 --- a/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap +++ b/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap @@ -1,9 +1,9 @@ exports[`NotFoundPage matches snapshot 1`] = ` - Home - + `; diff --git a/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap b/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap index cbd90ce7..fb8c4b88 100644 --- a/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap +++ b/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap @@ -1,7 +1,7 @@ exports[`OrderPage matches snapshot 1`] = ` - Booking details - - + Discussion - + ; +/* eslint-enable react/no-danger */ + const Topbar = props => { const { isAuthenticated, onLogout, user, push: historyPush } = props; - const house = { dangerouslySetInnerHTML: { __html: '🏠' } }; const hamburger = { dangerouslySetInnerHTML: { __html: '🍔' } }; const handleChange = e => { @@ -24,7 +28,9 @@ const Topbar = props => { return (
- + + +