Skip to content

Commit

Permalink
feat(breadcrumbs): add breadcrumbs
Browse files Browse the repository at this point in the history
  • Loading branch information
zouxuoz committed Aug 20, 2018
1 parent ce13fdc commit c081230
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 37 deletions.
52 changes: 52 additions & 0 deletions src/atoms/Breadcrumbs/Breadcrumbs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @flow
/* eslint-disable react/jsx-key */
import React, { Fragment } from 'react';

import { createStyledTag, createTheme } from '../../utils';
import type { BreadcrumbsRoutes, BreadcrumbsMatchPath } from './Breadcrumbs.types';
import { getBreadcrumbs } from './Breadcrumbs.utils';
import { BreadcrumbsItem } from './BreadcrumbsItem';
import { BreadcrumbsDivider } from './BreadcrumbsDivider';

type BreadcrumbsProps = {|
/* the location pathname */
pathname: string,
/* list of breadcrumbs routes */
routes: BreadcrumbsRoutes,
/* custom match path function */
matchPath?: BreadcrumbsMatchPath,
/* custom breadcrum's item tag */
itemTagName?: string | React$ComponentType<*>,
|};

const name = 'breadcrumbs';

const theme = createTheme(name, {
modifiers: {
},
defaults: {
},
});

const BreadcrumbsTag = createStyledTag(name, {});

const Breadcrumbs = ({ itemTagName, pathname, routes, matchPath, ...rest }: BreadcrumbsProps) => {
const breadcrumbs = getBreadcrumbs(pathname, routes, matchPath);

return (
<BreadcrumbsTag { ...rest }>
{
React.Children.toArray(
breadcrumbs.map((item, index) => (
<Fragment>
<BreadcrumbsItem to={ item.originalPath } { ...{ ...rest, ...item, tagName: itemTagName } } />
{ index !== breadcrumbs.length - 1 && <BreadcrumbsDivider>></BreadcrumbsDivider> }
</Fragment>
)),
)
}
</BreadcrumbsTag>
);
};

export { Breadcrumbs, theme };
27 changes: 27 additions & 0 deletions src/atoms/Breadcrumbs/Breadcrumbs.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

const routes = [
{ path: '/', label: 'Dashboard' },
{ path: '/app', component: ({ param }) => param },
{ path: '/app/settings', label: 'Settings' },
{ path: '/app/settings/security', label: 'Security' },
];

export default (asStory) => {
asStory('ATOMS/Breadcrumbs', module, (story, { Breadcrumbs, Button, Link, Row }) => {
story
.add('with default modifiers', () => (
<Breadcrumbs pathname="/app/settings/security" routes={ routes } param={ 1 } />
));
story
.add('with custom tagName', () => (
<Breadcrumbs
tagName={ Row }
pathname="/app/settings/security"
routes={ routes }
param={ 1 }
itemTagName={ ({ label, ...rest }) => <Button tagName={ Link } { ...rest } text={ label } /> }
/>
));
});
};
8 changes: 8 additions & 0 deletions src/atoms/Breadcrumbs/Breadcrumbs.types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type BreadcrumbsRoutes = Array<{
path: string,
component?: React$ComponentType<*>,
label?: string,
matchOptions?: Object,
}>;

export type BreadcrumbsMatchPath = (path: string, options: Object) => boolean;
37 changes: 37 additions & 0 deletions src/atoms/Breadcrumbs/Breadcrumbs.utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @flow
import type { BreadcrumbsRoutes, BreadcrumbsMatchPath } from './Breadcrumbs.types';

const getPaths = (pathname: string) => pathname.replace(/\/$/, '').split('/').reduce((result, path, index) => [
...result,
index < 2 ? `/${path}` : `${result[result.length - 1]}/${path}`,
], []);


const matchPathDefault: BreadcrumbsMatchPath = (path: string, options = {}) => path === options.path;

const getBreadcrumbs = (
pathname: string,
routes: BreadcrumbsRoutes,
matchPath: BreadcrumbsMatchPath = matchPathDefault,
) => {
const paths = getPaths(pathname);

const breadcrumbs = paths.reduce((result, path) => {
const matchedRoute = routes.find(
(route) => matchPath ? matchPath(path, { path: route.path, ...(route.matchOptions || {}) }) : route.path === path,
);

if (matchedRoute) {
const match = matchPath(path, { path: matchedRoute.path, ...(matchedRoute.matchOptions || {}) });

result = [...result, { ...matchedRoute, originalPath: path, match }];
}

return result;
}, []);

return breadcrumbs;
};


export { getBreadcrumbs };
10 changes: 10 additions & 0 deletions src/atoms/Breadcrumbs/BreadcrumbsDivider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow
import styled from 'react-emotion';

const BreadcrumbsDivider = styled('span')({
padding: '0 1rem',
fontSize: '1.6rem',
fontFamily: 'Poppins',
});

export { BreadcrumbsDivider };
25 changes: 25 additions & 0 deletions src/atoms/Breadcrumbs/BreadcrumbsItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @flow
import React from 'react';

import { Link } from '../typography/Link';

type BreadcrumbsItemProps = {|
tagName: string | React$ComponentType<*>,
to: string,
label?: string,
component?: React$ComponentType<*>,
|};

const BreadcrumbsItem = ({ tagName, to, label, component: Component, ...rest }: BreadcrumbsItemProps) => React.createElement(
tagName,
{
to,
},
Component ? <Component { ...rest } to={ to } label={ label } /> : label,
);

BreadcrumbsItem.defaultProps = {
tagName: Link,
};

export { BreadcrumbsItem };
1 change: 1 addition & 0 deletions src/atoms/Breadcrumbs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Breadcrumbs';
1 change: 1 addition & 0 deletions src/atoms/atoms.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

export { Avatar } from './Avatar';
export { Button } from './Button';
export { Breadcrumbs } from './Breadcrumbs';
export { Card } from './Card';
export { Checkbox } from './dataEntry/Checkbox';
export { CheckboxField } from './dataEntry/CheckboxField';
Expand Down
2 changes: 2 additions & 0 deletions src/atoms/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { theme as avatarTheme } from './Avatar';
import { theme as buttonTheme } from './Button';
import { theme as breadcrumbsTheme } from './Breadcrumbs';
import { theme as cardTheme } from './Card';
import { theme as checkboxTheme } from './dataEntry/Checkbox';
import { theme as dialogTheme } from './Dialog';
Expand Down Expand Up @@ -32,6 +33,7 @@ import { theme as textTheme } from './typography/Text';
export const theme = {
...avatarTheme,
...buttonTheme,
...breadcrumbsTheme,
...cardTheme,
...checkboxTheme,
...dialogTheme,
Expand Down
Loading

0 comments on commit c081230

Please sign in to comment.