diff --git a/src/components/Breadcrumb/Breadcrumb-story.js b/src/components/Breadcrumb/Breadcrumb-story.js index 6df2206d4e..96dda67151 100644 --- a/src/components/Breadcrumb/Breadcrumb-story.js +++ b/src/components/Breadcrumb/Breadcrumb-story.js @@ -11,9 +11,8 @@ 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', @@ -21,10 +20,10 @@ const props = () => ({ onClick: action('onClick'), }); -storiesOf('Breadcrumb', module) +const breadcrumbStory = storiesOf('Breadcrumb', module) .addDecorator(withKnobs) .add( - 'Default', + 'default', () => ( @@ -42,6 +41,24 @@ storiesOf('Breadcrumb', module) }, } ) + .add( + 'no trailing slash', + () => ( + + + Breadcrumb 1 + + Breadcrumb 2 + Breadcrumb 3 + + ), + { + info: { + text: + 'You can choose not to render a trailing slash with the `noTrailingSlash` prop', + }, + } + ) .add('skeleton', () => , { info: { text: ` @@ -49,3 +66,47 @@ storiesOf('Breadcrumb', module) `, }, }); + +if (FeatureFlags.componentsX) { + breadcrumbStory + .add( + 'current page', + () => ( + + + Breadcrumb 1 + + Breadcrumb 2 + + Breadcrumb 3 + + + ), + { + info: { + text: + 'You can specify a BreadcrumbItem component as the current page with the `isCurrentPage` prop', + }, + } + ) + .add( + 'current page with aria-current', + () => ( + + + Breadcrumb 1 + + Breadcrumb 2 + + Breadcrumb 3 + + + ), + { + info: { + text: + 'You can specify a BreadcrumbItem component as the current page with the `aria-current` prop by specifying `aria-current="page"`', + }, + } + ); +} diff --git a/src/components/Breadcrumb/Breadcrumb-test.js b/src/components/Breadcrumb/Breadcrumb-test.js deleted file mode 100644 index 32be9d7556..0000000000 --- a/src/components/Breadcrumb/Breadcrumb-test.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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 Breadcrumb from '../Breadcrumb'; -import BreadcrumbItem from '../BreadcrumbItem'; -import BreadcrumbSkeleton from '../Breadcrumb/Breadcrumb.Skeleton'; -import { mount, shallow } from 'enzyme'; - -describe('Breadcrumb', () => { - describe('Renders as expected', () => { - const breadcrumb = mount( - - - Breadcrumb 1 - - - ); - - const breadcrumbItem = breadcrumb.find(BreadcrumbItem); - - it('renders a breadcrumb', () => { - expect(breadcrumb.length).toEqual(1); - }); - - it('should use the appropriate breadcrumb class', () => { - expect(breadcrumb.children().hasClass('bx--breadcrumb')).toEqual(true); - }); - - it('should add extra classes that are passed via className', () => { - expect(breadcrumb.hasClass('parent-class')).toEqual(true); - }); - - it('should render children as expected', () => { - expect(breadcrumb.find(BreadcrumbItem).length).toEqual(1); - }); - - it('should render children content as expected', () => { - expect(breadcrumbItem.text()).toEqual('Breadcrumb 1'); - }); - }); -}); - -describe('BreadcrumbSkeleton', () => { - describe('Renders as expected', () => { - const wrapper = shallow(); - - it('Has the expected classes', () => { - expect(wrapper.hasClass('bx--skeleton')).toEqual(true); - expect(wrapper.hasClass('bx--breadcrumb')).toEqual(true); - }); - }); -}); diff --git a/src/components/Breadcrumb/Breadcrumb.js b/src/components/Breadcrumb/Breadcrumb.js index 860faa5535..94f03a4de7 100644 --- a/src/components/Breadcrumb/Breadcrumb.js +++ b/src/components/Breadcrumb/Breadcrumb.js @@ -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 ( + + ); + } + return ( -
+
{children}
); diff --git a/src/components/Breadcrumb/BreadcrumbItem.js b/src/components/Breadcrumb/BreadcrumbItem.js new file mode 100644 index 0000000000..9b752074dd --- /dev/null +++ b/src/components/Breadcrumb/BreadcrumbItem.js @@ -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 ( +
+ + {children} + +
+ ); + } + + return ( +
+ {React.cloneElement(children, { + 'aria-current': ariaCurrent, + className: `${prefix}--link`, + })} +
+ ); +}; + +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; diff --git a/src/components/Breadcrumb/__tests__/Breadcrumb-test.js b/src/components/Breadcrumb/__tests__/Breadcrumb-test.js new file mode 100644 index 0000000000..a514fbce9e --- /dev/null +++ b/src/components/Breadcrumb/__tests__/Breadcrumb-test.js @@ -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 1 + + + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should support rendering without a trailing slash', () => { + const wrapper = mount( + + + Breadcrumb 1 + + + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should support rendering a custom component as a breadcrumb item', () => { + const CustomComponent = jest.fn(({ children, href, ...rest }) => ( + + {children} + + )); + + mount( + + A + B + + C + + + ); + + 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 1 + + + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should support rendering the current page', () => { + const manual = mount( + + A + B + + C + + + ); + expect(manual).toMatchSnapshot(); + + const aria = mount( + + A + B + + C + + + ); + expect(aria).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/Breadcrumb/__tests__/Breadcrumb.Skeleton-test.js b/src/components/Breadcrumb/__tests__/Breadcrumb.Skeleton-test.js new file mode 100644 index 0000000000..92b13f3401 --- /dev/null +++ b/src/components/Breadcrumb/__tests__/Breadcrumb.Skeleton-test.js @@ -0,0 +1,17 @@ +/** + * 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 { BreadcrumbSkeleton } from '../'; +import { mount } from 'enzyme'; + +describe('BreadcrumbSkeleton', () => { + it('should render', () => { + const wrapper = mount(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb-test.js.snap b/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb-test.js.snap new file mode 100644 index 0000000000..4b8c07298c --- /dev/null +++ b/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb-test.js.snap @@ -0,0 +1,220 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Breadcrumb experimental should render 1`] = ` + + + +`; + +exports[`Breadcrumb experimental should support rendering the current page 1`] = ` + + + +`; + +exports[`Breadcrumb experimental should support rendering the current page 2`] = ` + + + +`; + +exports[`Breadcrumb should render 1`] = ` + +
+ + + +
+
+`; + +exports[`Breadcrumb should support rendering without a trailing slash 1`] = ` + +
+ + + +
+
+`; diff --git a/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.Skeleton-test.js.snap b/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.Skeleton-test.js.snap new file mode 100644 index 0000000000..f062545bcc --- /dev/null +++ b/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.Skeleton-test.js.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BreadcrumbSkeleton should render 1`] = ` + +
+ + + +
+
+`; diff --git a/src/components/Breadcrumb/index.js b/src/components/Breadcrumb/index.js index b5ad3a48a2..d9eede4f35 100644 --- a/src/components/Breadcrumb/index.js +++ b/src/components/Breadcrumb/index.js @@ -5,5 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -export * from './Breadcrumb.Skeleton'; -export default from './Breadcrumb'; +import Breadcrumb from './Breadcrumb'; +import BreadcrumbItem from './BreadcrumbItem'; +import BreadcrumbSkeleton from './Breadcrumb.Skeleton'; + +export { Breadcrumb, BreadcrumbItem, BreadcrumbSkeleton }; + +// Maintain default export as Breadcrumb for backwards-compatability +export default Breadcrumb; diff --git a/src/components/BreadcrumbItem/BreadcrumbItem.js b/src/components/BreadcrumbItem/BreadcrumbItem.js deleted file mode 100644 index 4e2208d2a0..0000000000 --- a/src/components/BreadcrumbItem/BreadcrumbItem.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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 classnames from 'classnames'; -import { settings } from 'carbon-components'; -import Link from '../Link'; - -const { prefix } = settings; - -const newChild = (children, href, prefix) => { - if (typeof children === 'string' && !(href === undefined)) { - return {children}; - } else { - return React.cloneElement(React.Children.only(children), { - className: `${prefix}--link`, - }); - } -}; - -const BreadcrumbItem = ({ children, className, href, ...other }) => { - const classNames = classnames(`${prefix}--breadcrumb-item`, className); - return ( -
- {newChild(children, href, prefix)} -
- ); -}; - -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, -}; - -export default BreadcrumbItem; diff --git a/src/components/BreadcrumbItem/index.js b/src/components/BreadcrumbItem/index.js index e06c98b13e..0852d2c073 100644 --- a/src/components/BreadcrumbItem/index.js +++ b/src/components/BreadcrumbItem/index.js @@ -5,4 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -export default from './BreadcrumbItem'; +// Alias for `BreadcrumbItem` that has been moved to `Breadcrumb` folder. In +// older versions of carbon, we had `BreadcrumbItem` in its own folder. +export default from '../Breadcrumb/BreadcrumbItem'; diff --git a/src/index.js b/src/index.js index 3d3d20ace2..38a3fe5536 100644 --- a/src/index.js +++ b/src/index.js @@ -7,8 +7,7 @@ export Accordion from './components/Accordion'; export AccordionItem from './components/AccordionItem'; -export Breadcrumb from './components/Breadcrumb'; -export BreadcrumbItem from './components/BreadcrumbItem'; +export { Breadcrumb, BreadcrumbItem } from './components/Breadcrumb'; export Button from './components/Button'; export Checkbox from './components/Checkbox'; export CodeSnippet from './components/CodeSnippet';