-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into slider-labels
- Loading branch information
Showing
7 changed files
with
434 additions
and
80 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
103 changes: 103 additions & 0 deletions
103
packages/react/src/components/ProgressIndicator/next/ProgressIndicator.js
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,103 @@ | ||
/** | ||
* 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 cx from 'classnames'; | ||
import PropTypes from 'prop-types'; | ||
import React, { useState } from 'react'; | ||
import { usePrefix } from '../../../internal/usePrefix'; | ||
|
||
function ProgressIndicator({ | ||
children, | ||
className: customClassName, | ||
currentIndex: controlledIndex = 0, | ||
onChange, | ||
spaceEqually, | ||
vertical, | ||
...rest | ||
}) { | ||
const prefix = usePrefix(); | ||
const [currentIndex, setCurrentIndex] = useState(controlledIndex); | ||
const [prevControlledIndex, setPrevControlledIndex] = useState( | ||
controlledIndex | ||
); | ||
const className = cx({ | ||
[`${prefix}--progress`]: true, | ||
[`${prefix}--progress--vertical`]: vertical, | ||
[`${prefix}--progress--space-equal`]: spaceEqually && !vertical, | ||
[customClassName]: customClassName, | ||
}); | ||
|
||
if (controlledIndex !== prevControlledIndex) { | ||
setCurrentIndex(controlledIndex); | ||
setPrevControlledIndex(controlledIndex); | ||
} | ||
|
||
return ( | ||
<ul className={className} {...rest}> | ||
{React.Children.map(children, (child, index) => { | ||
// only setup click handlers if onChange event is passed | ||
const onClick = onChange ? () => onChange(index) : undefined; | ||
if (index === currentIndex) { | ||
return React.cloneElement(child, { | ||
current: true, | ||
index, | ||
onClick, | ||
}); | ||
} | ||
if (index < currentIndex) { | ||
return React.cloneElement(child, { | ||
complete: true, | ||
index, | ||
onClick, | ||
}); | ||
} | ||
if (index > currentIndex) { | ||
return React.cloneElement(child, { | ||
complete: child.props.complete || false, | ||
index, | ||
onClick, | ||
}); | ||
} | ||
return null; | ||
})} | ||
</ul> | ||
); | ||
} | ||
|
||
ProgressIndicator.propTypes = { | ||
/** | ||
* Provide <ProgressStep> components to be rendered in the | ||
* <ProgressIndicator> | ||
*/ | ||
children: PropTypes.node, | ||
|
||
/** | ||
* Provide an optional className to be applied to the containing node | ||
*/ | ||
className: PropTypes.string, | ||
|
||
/** | ||
* Optionally specify the current step array index | ||
*/ | ||
currentIndex: PropTypes.number, | ||
|
||
/** | ||
* Optional callback called if a ProgressStep is clicked on. Returns the index of the step. | ||
*/ | ||
onChange: PropTypes.func, | ||
|
||
/** | ||
* Specify whether the progress steps should be split equally in size in the div | ||
*/ | ||
spaceEqually: PropTypes.bool, | ||
/** | ||
* Determines whether or not the ProgressIndicator should be rendered vertically. | ||
*/ | ||
vertical: PropTypes.bool, | ||
}; | ||
|
||
export { ProgressIndicator }; |
166 changes: 166 additions & 0 deletions
166
packages/react/src/components/ProgressIndicator/next/__tests__/ProgressIndicator-test.js
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,166 @@ | ||
/** | ||
* 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 { mount } from 'enzyme'; | ||
import React from 'react'; | ||
import { ProgressIndicator } from '../ProgressIndicator'; | ||
import { ProgressStep } from '../../'; | ||
|
||
const prefix = 'bx'; | ||
|
||
function getActiveIndex(wrapper) { | ||
return wrapper | ||
.find(`.${prefix}--progress-step--current`) | ||
.parent() | ||
.prop('index'); | ||
} | ||
|
||
describe('ProgressIndicator', () => { | ||
describe('Renders as expected', () => { | ||
let list; | ||
|
||
beforeEach(() => { | ||
list = mount( | ||
<ProgressIndicator className="some-class" currentIndex={3}> | ||
<ProgressStep | ||
label="label" | ||
description="Step 1: Getting Started with Node.js" | ||
/> | ||
<ProgressStep | ||
label="label" | ||
description="Step 2: Getting Started with Node.js" | ||
/> | ||
<ProgressStep | ||
label="label" | ||
description="Step 3: Getting Started with Node.js" | ||
/> | ||
<ProgressStep | ||
label="label" | ||
description="Step 4: Getting Started with Node.js" | ||
/> | ||
<ProgressStep | ||
label="label" | ||
description="Step 5: Getting Started with Node.js" | ||
/> | ||
<ProgressStep | ||
label="label" | ||
description="Step 6: Getting Started with Node.js" | ||
/> | ||
</ProgressIndicator> | ||
); | ||
}); | ||
|
||
it('should be a ul element', () => { | ||
expect(list.find('ul').length).toEqual(1); | ||
}); | ||
|
||
it('should render children as expected', () => { | ||
expect(list.find(ProgressStep).length).toEqual(6); | ||
}); | ||
|
||
it('should have the initial currentIndex from props', () => { | ||
expect(getActiveIndex(list)).toEqual(3); | ||
}); | ||
|
||
it('should update state when currentIndex is changed', () => { | ||
list.setProps({ currentIndex: 1 }); | ||
expect(getActiveIndex(list)).toEqual(1); | ||
list.setProps({ currentIndex: 0 }); | ||
expect(getActiveIndex(list)).toEqual(0); | ||
}); | ||
|
||
it('should trigger onChange if clicked', () => { | ||
const mockOnChange = jest.fn(); | ||
|
||
list.setProps({ onChange: mockOnChange }); | ||
list.find(ProgressStep).at(0).find('button').simulate('click'); | ||
expect(mockOnChange).toHaveBeenCalledWith(0); | ||
}); | ||
|
||
describe('ProgressStep', () => { | ||
it('should render with correct base className', () => { | ||
expect( | ||
list | ||
.find(ProgressStep) | ||
.at(0) | ||
.children() | ||
.hasClass(`${prefix}--progress-step`) | ||
).toEqual(true); | ||
}); | ||
|
||
it('should render with a label', () => { | ||
expect(list.find(ProgressStep).at(0).prop('label')).toEqual('label'); | ||
}); | ||
|
||
it('should render with a description', () => { | ||
expect(list.find(ProgressStep).at(0).prop('description')).toEqual( | ||
'Step 1: Getting Started with Node.js' | ||
); | ||
}); | ||
|
||
it('should render description in <title> node', () => { | ||
expect(list.find('ProgressStep title').at(0).text()).toEqual( | ||
'Step 1: Getting Started with Node.js' | ||
); | ||
}); | ||
|
||
describe('current', () => { | ||
it('should render a current ProgressStep with correct className', () => { | ||
expect( | ||
list | ||
.find(ProgressStep) | ||
.at(3) | ||
.children() | ||
.hasClass(`${prefix}--progress-step--current`) | ||
).toEqual(true); | ||
}); | ||
|
||
it('should render a current ProgressStep with correct props', () => { | ||
expect(list.find(ProgressStep).at(3).prop('current')).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('complete', () => { | ||
it('should render any completed ProgressSteps with correct className', () => { | ||
expect( | ||
list | ||
.find(ProgressStep) | ||
.at(0) | ||
.children() | ||
.hasClass(`${prefix}--progress-step--complete`) | ||
).toEqual(true); | ||
}); | ||
it('should render any completed ProgressSteps with correct props', () => { | ||
expect(list.find(ProgressStep).at(0).prop('complete')).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('incomplete', () => { | ||
it('should render any incomplete ProgressSteps with correct className', () => { | ||
expect( | ||
list | ||
.find(ProgressStep) | ||
.at(5) | ||
.children() | ||
.hasClass(`${prefix}--progress-step--incomplete`) | ||
).toEqual(true); | ||
}); | ||
it('should render any incomplete ProgressSteps with correct props', () => { | ||
expect(list.find(ProgressStep).at(5).prop('complete')).toBe(false); | ||
}); | ||
|
||
it('should render any clickable ProgressSteps with correct classname', () => { | ||
list.setProps({ onChange: jest.fn() }); | ||
expect(list.find(`.${prefix}--progress-step-button`)).toHaveLength(6); // one button for each div | ||
expect( | ||
list.find(`.${prefix}--progress-step-button--unclickable`) | ||
).toHaveLength(1); // only the current step should be unclickable | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.