Skip to content

Commit

Permalink
feat(InputSelect): handle children as React element & expose itemValu…
Browse files Browse the repository at this point in the history
…eMapper props (#660)

* feat(InputSelect): handle children as React element
  • Loading branch information
liuderchi authored and evenchange4 committed Jun 7, 2018
1 parent ffed90c commit 63ea99c
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 16 deletions.
51 changes: 51 additions & 0 deletions packages/mcs-lite-ui/src/InputSelect/InputSelect.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,57 @@ storiesOf('InputSelect', module)
/>
)),
)
.add(
'With children React element and itemValueMapper',
withInfo({
text: '',
inline: true,
})(() => (
<InputSelect
itemValueMapper={(item: any) => item.displayValue}
value={2}
onChange={action('onChange')}
items={[
{
value: 1,
displayValue: 'High',
children: (
<span>
<svg height="10" width="20">
<circle cx="5" cy="5" r="5" fill="red" />
</svg>
High
</span>
),
},
{
value: 2,
displayValue: 'Medium',
children: (
<span>
<svg height="10" width="20">
<circle cx="5" cy="5" r="5" fill="orange" />
</svg>
Medium
</span>
),
},
{
value: 3,
displayValue: 'Low',
children: (
<span>
<svg height="10" width="20">
<circle cx="5" cy="5" r="5" fill="gold" />
</svg>
Low
</span>
),
},
]}
/>
)),
)
.add(
'With disableFilter props',
withInfo({
Expand Down
37 changes: 28 additions & 9 deletions packages/mcs-lite-ui/src/InputSelect/InputSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
FakeInputValue,
TextOverflow,
} from './styled-components';
import { type Value, type ItemProps } from './type.flow';
import { type Value, type ItemProps, type ItemValueMapper } from './type.flow';
import {
filterByChildren,
filterBy,
getInputValue,
getPlaceholder,
getMenuItemHeight,
Expand All @@ -41,10 +41,13 @@ type Props = {
focus?: boolean,
noRowsRenderer?: ({ onClose: () => void }) => React.Node,
disableFilter?: boolean,
itemValueMapper?: ItemValueMapper,
// Note: innerRef for the problem of outside click in dialog
menuRef?: (ref: React.ElementRef<typeof StyledMenu>) => void,
};

const defaultItemValueMapper = (item: ItemProps) => item.children;

class PureInputSelect extends React.Component<
Props & { theme: Object },
{
Expand All @@ -57,6 +60,7 @@ class PureInputSelect extends React.Component<
kind: 'primary',
noRowsRenderer: () => <NoRowWrapper>No results found</NoRowWrapper>,
disableFilter: false,
itemValueMapper: defaultItemValueMapper,
};
state = { isOpen: false, filter: '', menuWidth: 0 };
componentDidMount() {
Expand Down Expand Up @@ -108,10 +112,15 @@ class PureInputSelect extends React.Component<
index: number,
style: Object,
}): React.Element<typeof MenuItem> => {
const { items, value, onChange } = this.props;
const {
items,
value,
onChange,
itemValueMapper = defaultItemValueMapper,
} = this.props;
const { filter } = this.state;
const { onClose } = this;
const filteredItems = filterByChildren(items, filter);
const filteredItems = filterBy({ items, filter, itemValueMapper });
const { value: itemValue, children }: ItemProps = filteredItems[index];
const onItemClick = () => {
onChange(itemValue);
Expand Down Expand Up @@ -144,6 +153,7 @@ class PureInputSelect extends React.Component<
focus,
menuRef,
disableFilter,
itemValueMapper = defaultItemValueMapper,
...otherProps
} = this.props;
const { menuWidth, isOpen, filter } = this.state;
Expand All @@ -158,7 +168,7 @@ class PureInputSelect extends React.Component<
onClickOutside,
} = this;

const filteredItems = filterByChildren(items, filter);
const filteredItems = filterBy({ items, filter, itemValueMapper });
const activeIndex = R.findIndex(R.propEq('value', value))(items);
const activeItem = items[activeIndex];
const menuItemHeight = getMenuItemHeight(theme);
Expand All @@ -175,7 +185,12 @@ class PureInputSelect extends React.Component<
ref={onInputRef}
kind={kind}
focus={focus || isOpen}
value={getInputValue({ isOpen, filter, activeItem })}
value={getInputValue({
isOpen,
filter,
activeItem,
itemValueMapper,
})}
onChange={onFilterChange}
placeholder={getPlaceholder({ isOpen, activeItem, placeholder })}
readOnly={disableFilter}
Expand All @@ -186,7 +201,9 @@ class PureInputSelect extends React.Component<
/>
{isOpen &&
activeItem &&
!filter && <FakeInputValue defaultValue={activeItem.children} />}
!filter && (
<FakeInputValue defaultValue={itemValueMapper(activeItem)} />
)}
<StyledButton
kind={kind}
active={focus || isOpen}
Expand Down Expand Up @@ -239,15 +256,17 @@ InputSelect.propTypes = {
onChange: PropTypes.func.isRequired, // (value: Value) => Promise<void> | void,
items: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
children: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
.isRequired,
children: PropTypes.node.isRequired,
}),
).isRequired,
kind: PropTypes.string,
placeholder: PropTypes.string,
noRowsRenderer: PropTypes.func,
focus: PropTypes.bool,
disableFilter: PropTypes.bool,
itemValueMapper: PropTypes.func,
menuRef: PropTypes.func,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,167 @@ exports[`Storyshots InputSelect With autoFocus props 1`] = `
</div>
`;

exports[`Storyshots InputSelect With children React element and itemValueMapper 1`] = `
.c3 {
border-width: 1px;
border-style: solid;
outline: none;
box-sizing: border-box;
-webkit-transition: background-color cubic-bezier(0.47,0,0.75,0.72) 0.3s;
transition: background-color cubic-bezier(0.47,0,0.75,0.72) 0.3s;
line-height: 0;
cursor: pointer;
background-color: #00A1DE;
color: #FFFFFF;
border-radius: 3px;
width: 32px;
min-width: initial;
height: 32px;
padding: 0;
font-size: 1rem;
border-color: rgb(0,130,179);
font-size: 18px;
color: #FFFFFF;
}

.c3:hover {
background-color: rgb(0,151,208);
}

.c3:active {
background-color: rgb(0,144,198);
}

.c2 {
box-sizing: border-box;
width: 100%;
border-width: 1px;
border-style: solid;
border-radius: 3px;
outline: 0;
padding: 0 10px;
line-height: 1;
min-height: 32px;
color: #353630;
font-size: 1rem;
border-color: #D1D2D3;
box-shadow: none;
}

.c2:focus {
border-color: #00A1DE;
box-shadow: 0 0 3px 0 rgba(0,161,222,0.5);
}

.c2::-webkit-input-placeholder {
opacity: 1;
color: #D1D2D3;
}

.c2::-moz-placeholder {
opacity: 1;
color: #D1D2D3;
}

.c2:-ms-input-placeholder {
opacity: 1;
color: #D1D2D3;
}

.c2::placeholder {
opacity: 1;
color: #D1D2D3;
}

.c4 {
line-height: 0;
}

.c4 > * {
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
-webkit-transition: -webkit-transform 0.4s cubic-bezier(0.68,-0.55,0.27,1.55);
-webkit-transition: transform 0.4s cubic-bezier(0.68,-0.55,0.27,1.55);
transition: transform 0.4s cubic-bezier(0.68,-0.55,0.27,1.55);
-webkit-transform: initial;
-ms-transform: initial;
transform: initial;
}

.c0 {
position: relative;
}

.c1 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}

.c1 > button {
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
}

.c1 > *:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

.c1 > *:not(:first-child):not(:last-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

.c1 > *:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

<div>
<div
className="c0 c1"
>
<input
autoComplete="off"
className="c2"
kind="primary"
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder=""
readOnly={false}
value="Medium"
/>
<button
className="c3"
onClick={[Function]}
>
<div
className="c4"
>
<svg
fill="currentColor"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
width="1em"
>
<path
d="M7.4 9.8l4.6 4.6 4.6-4.6 1.4 1.4-6 6-6-6 1.4-1.4z"
/>
</svg>
</div>
</button>
</div>
</div>
`;

exports[`Storyshots InputSelect With disableFilter props 1`] = `
.c3 {
border-width: 1px;
Expand Down
6 changes: 5 additions & 1 deletion packages/mcs-lite-ui/src/InputSelect/type.flow.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// @flow
import * as React from 'react';

export type Value = number | string;
export type ItemProps = {
value: Value,
children: string,
children: React.Node,
};

export type ItemValueMapper = ItemProps => string;
17 changes: 12 additions & 5 deletions packages/mcs-lite-ui/src/InputSelect/utils.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
// @flow
import * as R from 'ramda';
import { type ItemProps } from './type.flow';
import { type ItemProps, type ItemValueMapper } from './type.flow';

export const MAX_HEIGHT = 155;

export function filterByChildren(
export function filterBy({
items,
filter,
itemValueMapper,
}: {
items: Array<ItemProps>,
filter: string,
): Array<ItemProps> {
itemValueMapper: ItemValueMapper,
}): Array<ItemProps> {
const regex = new RegExp(filter, 'gi');
return items.filter(({ children }: ItemProps) => regex.test(children));
return items.filter((item: ItemProps) => regex.test(itemValueMapper(item)));
}

export function getInputValue({
isOpen,
filter,
activeItem,
itemValueMapper,
}: {
isOpen: boolean,
filter: string,
activeItem?: ItemProps,
itemValueMapper: ItemValueMapper,
}): string {
let value;

if (isOpen) {
value = filter;
} else {
value = activeItem ? activeItem.children : '';
value = activeItem ? itemValueMapper(activeItem) : '';
}

return value;
Expand Down
Loading

0 comments on commit 63ea99c

Please sign in to comment.