Skip to content

Commit

Permalink
feat(breadcrumb): add support for current page (carbon-design-system#…
Browse files Browse the repository at this point in the history
…2054)

* feat(breadcrumb): add support for current page

* chore(breadcrumb): add new prop types behind feature flag

* chore(breadcrumb): update imports in storybook

* Update Breadcrumb-story.js
  • Loading branch information
joshblack authored and tw15egan committed Mar 20, 2019
1 parent 9b6659d commit 9e59584
Show file tree
Hide file tree
Showing 12 changed files with 575 additions and 123 deletions.
71 changes: 66 additions & 5 deletions src/components/Breadcrumb/Breadcrumb-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean } from '@storybook/addon-knobs';
import Breadcrumb from '../Breadcrumb';
import BreadcrumbItem from '../BreadcrumbItem';
import BreadcrumbSkeleton from '../Breadcrumb/Breadcrumb.Skeleton';
import { Breadcrumb, BreadcrumbItem, BreadcrumbSkeleton } from '../Breadcrumb';
import * as FeatureFlags from '../../internal/FeatureFlags';

const props = () => ({
className: 'some-class',
noTrailingSlash: boolean('No trailing slash (noTrailingSlash)', false),
onClick: action('onClick'),
});

storiesOf('Breadcrumb', module)
const breadcrumbStory = storiesOf('Breadcrumb', module)
.addDecorator(withKnobs)
.add(
'Default',
'default',
() => (
<Breadcrumb {...props()}>
<BreadcrumbItem>
Expand All @@ -42,10 +41,72 @@ storiesOf('Breadcrumb', module)
},
}
)
.add(
'no trailing slash',
() => (
<Breadcrumb {...props()} noTrailingSlash>
<BreadcrumbItem>
<a href="/#">Breadcrumb 1</a>
</BreadcrumbItem>
<BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem>
<BreadcrumbItem href="#">Breadcrumb 3</BreadcrumbItem>
</Breadcrumb>
),
{
info: {
text:
'You can choose not to render a trailing slash with the `noTrailingSlash` prop',
},
}
)
.add('skeleton', () => <BreadcrumbSkeleton />, {
info: {
text: `
Placeholder skeleton state to use when content is loading.
`,
},
});

if (FeatureFlags.componentsX) {
breadcrumbStory
.add(
'current page',
() => (
<Breadcrumb {...props()}>
<BreadcrumbItem>
<a href="/#">Breadcrumb 1</a>
</BreadcrumbItem>
<BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem>
<BreadcrumbItem href="#" isCurrentPage>
Breadcrumb 3
</BreadcrumbItem>
</Breadcrumb>
),
{
info: {
text:
'You can specify a BreadcrumbItem component as the current page with the `isCurrentPage` prop',
},
}
)
.add(
'current page with aria-current',
() => (
<Breadcrumb {...props()}>
<BreadcrumbItem>
<a href="/#">Breadcrumb 1</a>
</BreadcrumbItem>
<BreadcrumbItem href="#">Breadcrumb 2</BreadcrumbItem>
<BreadcrumbItem href="#" aria-current="page">
Breadcrumb 3
</BreadcrumbItem>
</Breadcrumb>
),
{
info: {
text:
'You can specify a BreadcrumbItem component as the current page with the `aria-current` prop by specifying `aria-current="page"`',
},
}
);
}
57 changes: 0 additions & 57 deletions src/components/Breadcrumb/Breadcrumb-test.js

This file was deleted.

24 changes: 20 additions & 4 deletions src/components/Breadcrumb/Breadcrumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,34 @@

import PropTypes from 'prop-types';
import React from 'react';
import classnames from 'classnames';
import cx from 'classnames';
import { settings } from 'carbon-components';
import { componentsX } from '../../internal/FeatureFlags';

const { prefix } = settings;

const Breadcrumb = ({ children, className, noTrailingSlash, ...other }) => {
const classNames = classnames(className, {
const Breadcrumb = ({
children,
className: customClassName,
noTrailingSlash,
...rest
}) => {
const className = cx({
[`${prefix}--breadcrumb`]: true,
[`${prefix}--breadcrumb--no-trailing-slash`]: noTrailingSlash,
[customClassName]: !!customClassName,
});

if (componentsX) {
return (
<nav className={className} {...rest}>
{children}
</nav>
);
}

return (
<div className={classNames} {...other}>
<div className={className} {...rest}>
{children}
</div>
);
Expand Down
78 changes: 78 additions & 0 deletions src/components/Breadcrumb/BreadcrumbItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import PropTypes from 'prop-types';
import React from 'react';
import cx from 'classnames';
import { settings } from 'carbon-components';
import Link from '../Link';
import { componentsX } from '../../internal/FeatureFlags';

const { prefix } = settings;

const BreadcrumbItem = ({
'aria-current': ariaCurrent,
children,
className: customClassName,
href,
isCurrentPage,
...rest
}) => {
const className = cx({
[`${prefix}--breadcrumb-item`]: true,
// We set the current class only if `isCurrentPage` is passed in and we do
// not have an `aria-current="page"` set for the breadcrumb item
[`${prefix}--breadcrumb-item--current`]:
componentsX && isCurrentPage && ariaCurrent !== 'page',
[customClassName]: !!customClassName,
});

if (typeof children === 'string' && href) {
return (
<div className={className} {...rest}>
<Link href={href} aria-current={ariaCurrent}>
{children}
</Link>
</div>
);
}

return (
<div className={className} {...rest}>
{React.cloneElement(children, {
'aria-current': ariaCurrent,
className: `${prefix}--link`,
})}
</div>
);
};

BreadcrumbItem.propTypes = {
/**
* Pass in content that will be inside of the BreadcrumbItem
*/
children: PropTypes.node,
/**
* Specify an optional className to be applied to the container node
*/
className: PropTypes.string,

/**
* Optional string representing the link location for the BreadcrumbItem
*/
href: PropTypes.string,
};

if (componentsX) {
BreadcrumbItem.propTypes.children = PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]).isRequired;
BreadcrumbItem.propTypes.isCurrentPage = PropTypes.bool;
}

export default BreadcrumbItem;
122 changes: 122 additions & 0 deletions src/components/Breadcrumb/__tests__/Breadcrumb-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import { mount } from 'enzyme';

describe('Breadcrumb', () => {
let Breadcrumb;
let BreadcrumbItem;

beforeEach(() => {
jest.resetModules();
const BreadcrumbEntrypoint = require('../');
Breadcrumb = BreadcrumbEntrypoint.Breadcrumb;
BreadcrumbItem = BreadcrumbEntrypoint.BreadcrumbItem;
});

it('should render', () => {
const wrapper = mount(
<Breadcrumb className="parent-class">
<BreadcrumbItem
className="some-class"
href="www.carbondesignsystem.com">
Breadcrumb 1
</BreadcrumbItem>
</Breadcrumb>
);
expect(wrapper).toMatchSnapshot();
});

it('should support rendering without a trailing slash', () => {
const wrapper = mount(
<Breadcrumb noTrailingSlash>
<BreadcrumbItem href="www.carbondesignsystem.com">
Breadcrumb 1
</BreadcrumbItem>
</Breadcrumb>
);
expect(wrapper).toMatchSnapshot();
});

it('should support rendering a custom component as a breadcrumb item', () => {
const CustomComponent = jest.fn(({ children, href, ...rest }) => (
<a href={href} data-test-id="custom-component" {...rest}>
{children}
</a>
));

mount(
<Breadcrumb>
<BreadcrumbItem href="#a">A</BreadcrumbItem>
<BreadcrumbItem href="#b">B</BreadcrumbItem>
<BreadcrumbItem>
<CustomComponent href="#c">C</CustomComponent>
</BreadcrumbItem>
</Breadcrumb>
);

expect(CustomComponent).toHaveBeenCalled();
expect(CustomComponent).toHaveBeenCalledWith(
expect.objectContaining({
className: 'bx--link',
}),
{}
);
});

describe('experimental', () => {
let Breadcrumb;
let BreadcrumbItem;

beforeEach(() => {
jest.resetModules();
const FeatureFlags = require('../../../internal/FeatureFlags');
FeatureFlags.componentsX = true;
const BreadcrumbEntrypoint = require('../');
Breadcrumb = BreadcrumbEntrypoint.Breadcrumb;
BreadcrumbItem = BreadcrumbEntrypoint.BreadcrumbItem;
});

it('should render', () => {
const wrapper = mount(
<Breadcrumb className="parent-class">
<BreadcrumbItem
className="some-class"
href="www.carbondesignsystem.com">
Breadcrumb 1
</BreadcrumbItem>
</Breadcrumb>
);
expect(wrapper).toMatchSnapshot();
});

it('should support rendering the current page', () => {
const manual = mount(
<Breadcrumb>
<BreadcrumbItem href="#a">A</BreadcrumbItem>
<BreadcrumbItem href="#b">B</BreadcrumbItem>
<BreadcrumbItem href="#c" isCurrentPage>
C
</BreadcrumbItem>
</Breadcrumb>
);
expect(manual).toMatchSnapshot();

const aria = mount(
<Breadcrumb>
<BreadcrumbItem href="#a">A</BreadcrumbItem>
<BreadcrumbItem href="#b">B</BreadcrumbItem>
<BreadcrumbItem href="#c" aria-current="page">
C
</BreadcrumbItem>
</Breadcrumb>
);
expect(aria).toMatchSnapshot();
});
});
});
Loading

0 comments on commit 9e59584

Please sign in to comment.