Skip to content

Commit

Permalink
Merge ec21d1c into 4ef308c
Browse files Browse the repository at this point in the history
  • Loading branch information
drinchev committed Nov 19, 2019
2 parents 4ef308c + ec21d1c commit 56334e0
Show file tree
Hide file tree
Showing 13 changed files with 524 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/components/InputList/InputList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import classnames from 'classnames';
import { func, oneOf, string, arrayOf, shape, oneOfType } from 'prop-types';
import React from 'react';
import * as olt from '@lightelligence/styles';
import { InputListItem } from './InputListItem';

/**
* Input list is a list of items, used as the body of a dropdown. Can be used
* for any custom component
*
* This component uses a semantic `ul` html tag name and forwards refs
*
* The component also passes all other `props` to the underlying `ul` element
*/
export const InputList = React.forwardRef(
({ className, children, onChange, value, ...props }, ref) => (
<ul ref={ref} {...props} className={classnames(olt.InputList, className)}>
{React.Children.map(children, (child) =>
React.cloneElement(child, {
active: child.props.value === value,
onClick: onChange,
}),
)}
</ul>
),
);

InputList.displayName = 'InputList';

InputList.propTypes = {
/**
* Forward an additional className to the underlying element
*/
className: string,
/**
* Content of the element should always be consisted of
* [InputListItem](/#/Components/InputListItem) components.
*/
children: oneOfType([
shape({ type: oneOf([InputListItem]) }),
arrayOf(shape({ type: oneOf([InputListItem]) })),
]),
/**
* Callback when the value of the input list was changed
*/
onChange: func,
/**
* The current value of the input list
*/
value: string,
};

InputList.defaultProps = {
className: null,
children: null,
onChange: () => {},
value: null,
};
14 changes: 14 additions & 0 deletions src/components/InputList/InputList.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### Example

```jsx
import { InputList, InputListItem } from '@lightelligence/react';
const [value, setValue] = React.useState('2');
const onChange = (value) => {
setValue(value);
};
<InputList value={value} onChange={onChange}>
<InputListItem value="1">Item 1</InputListItem>
<InputListItem value="2">Item 2</InputListItem>
<InputListItem value="3">Item 3</InputListItem>
</InputList>;
```
61 changes: 61 additions & 0 deletions src/components/InputList/InputList.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';

import { InputList } from './InputList';
import { InputListItem } from './InputListItem';

const renderComponent = (props) => {
return render(<InputList {...props} data-testid="component" />);
};

describe('InputList', () => {
test('forwards className', () => {
const { getByTestId } = renderComponent({
className: 'myClass',
value: '1',
children: [
<InputListItem onClick={() => {}} value="1" />,
<InputListItem onClick={() => {}} value="2" />,
],
});
const component = getByTestId('component');
expect(component.classList.contains('myClass')).toBe(true);
});
test('properly sets value', () => {
const { getByText } = renderComponent({
className: 'myClass',
value: '1',
children: [
<InputListItem onClick={() => {}} value="1">
Item Foo
</InputListItem>,
<InputListItem onClick={() => {}} value="2">
Item Bar
</InputListItem>,
],
});
const itemFoo = getByText('Item Foo');
const itemBar = getByText('Item Bar');
expect(itemFoo.classList.contains('is-active')).toBe(true);
expect(itemBar.classList.contains('is-active')).toBe(false);
});
test('properly propagates onChange', () => {
const onChange = jest.fn();
const { getByText } = renderComponent({
className: 'myClass',
onChange,
value: '1',
children: [
<InputListItem onClick={() => {}} value="1">
Item Foo
</InputListItem>,
<InputListItem onClick={() => {}} value="2">
Item Bar
</InputListItem>,
],
});
const itemBar = getByText('Item Bar');
fireEvent.click(itemBar);
expect(onChange).toHaveBeenCalledWith('2', expect.anything());
});
});
106 changes: 106 additions & 0 deletions src/components/InputList/InputListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import classnames from 'classnames';
import { bool, node, string, func, number } from 'prop-types';
import React, { useCallback } from 'react';
import * as olt from '@lightelligence/styles';

/**
* List item for the [InputList](#/Components/InputList) Component
*
* This component uses a semantic `li` html tag name and forwards refs
*
* The component also passes all other `props` to the underlying `li` element
*/
export const InputListItem = React.forwardRef(
(
{
className,
children,
active,
onClick,
onKeyPress,
value,
tabIndex,
...props
},
ref,
) => {
const handleClick = useCallback((event) => onClick(value, event), [
value,
onClick,
]);
const handleKeyPress = useCallback((event) => onKeyPress(value, event), [
value,
onKeyPress,
]);

return (
<li>
<a
role="button"
ref={ref}
tabIndex={tabIndex}
onClick={handleClick}
onKeyPress={handleKeyPress}
className={classnames(
olt.InputListLink,
active && 'is-active',
className,
)}
{...props}
>
{children}
</a>
</li>
);
},
);

InputListItem.displayName = 'InputListItem';

InputListItem.propTypes = {
/**
* Forward an additional className to the underlying element
*/
className: string,
/**
* Content of the element. Can be any valid React node.
*/
children: node,
/**
* The value of the item when it's being selected
*/
value: string.isRequired,
/**
* Tab index
*
* @hidden
*/
tabIndex: number,
/**
* Is the item currently active
*
* @hidden
*/
active: bool,
/**
* Specifies what happens when the item is clicked
*
* @hidden
*/
onClick: func,
/**
* Specifies what happens on key press
*
* @hidden
*/
onKeyPress: func,
};

InputListItem.defaultProps = {
className: null,
children: null,
active: false,
onKeyPress: null,
tabIndex: null,
onClick: () => {},
};
14 changes: 14 additions & 0 deletions src/components/InputList/InputListItem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

### Example

```jsx
import { InputList, InputListItem } from '@lightelligence/react';
const onChange = (value) => {
console.log(`changed to ${value}`);
};
<InputList onChange={onChange}>
<InputListItem value="1">Item 1</InputListItem>
<InputListItem value="2">Item 2</InputListItem>
<InputListItem value="3">Item 3</InputListItem>
</InputList>;
```
45 changes: 45 additions & 0 deletions src/components/InputList/InputListItem.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';

import { InputListItem } from './InputListItem';

const renderComponent = (props) => {
return render(<InputListItem {...props} />);
};

describe('InputListItem', () => {
test('forwards className', () => {
const { getByText } = renderComponent({
className: 'myClass',
onClick: () => {},
value: '1',
children: 'Component',
});

const component = getByText('Component');
expect(component.classList.contains('myClass')).toBe(true);
});
test('forwards onClick', () => {
const onClick = jest.fn();
const { getByText } = renderComponent({
className: 'myClass',
onClick,
value: '1',
children: 'Component',
});
const component = getByText('Component');
fireEvent.click(component);
expect(onClick).toHaveBeenCalledWith('1', expect.anything());
});
test('forwards active', () => {
const { getByText } = renderComponent({
className: 'myClass',
onClick: () => {},
value: '1',
active: true,
children: 'Component',
});
const component = getByText('Component');
expect(component.classList.contains('is-active')).toBe(true);
});
});
2 changes: 2 additions & 0 deletions src/components/InputList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { InputList } from './InputList';
export { InputListItem } from './InputListItem';
2 changes: 2 additions & 0 deletions src/components/V2Button/V2Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const V2Button = React.forwardRef(
},
);

V2Button.displayName = 'V2Button';

V2Button.propTypes = {
/**
* The html tag that should be rendered for this button.
Expand Down

0 comments on commit 56334e0

Please sign in to comment.