Skip to content

Commit 63ea99c

Browse files
liuderchievenchange4
authored andcommitted
feat(InputSelect): handle children as React element & expose itemValueMapper props (#660)
* feat(InputSelect): handle children as React element
1 parent ffed90c commit 63ea99c

File tree

6 files changed

+258
-16
lines changed

6 files changed

+258
-16
lines changed

packages/mcs-lite-ui/src/InputSelect/InputSelect.example.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,57 @@ storiesOf('InputSelect', module)
167167
/>
168168
)),
169169
)
170+
.add(
171+
'With children React element and itemValueMapper',
172+
withInfo({
173+
text: '',
174+
inline: true,
175+
})(() => (
176+
<InputSelect
177+
itemValueMapper={(item: any) => item.displayValue}
178+
value={2}
179+
onChange={action('onChange')}
180+
items={[
181+
{
182+
value: 1,
183+
displayValue: 'High',
184+
children: (
185+
<span>
186+
<svg height="10" width="20">
187+
<circle cx="5" cy="5" r="5" fill="red" />
188+
</svg>
189+
High
190+
</span>
191+
),
192+
},
193+
{
194+
value: 2,
195+
displayValue: 'Medium',
196+
children: (
197+
<span>
198+
<svg height="10" width="20">
199+
<circle cx="5" cy="5" r="5" fill="orange" />
200+
</svg>
201+
Medium
202+
</span>
203+
),
204+
},
205+
{
206+
value: 3,
207+
displayValue: 'Low',
208+
children: (
209+
<span>
210+
<svg height="10" width="20">
211+
<circle cx="5" cy="5" r="5" fill="gold" />
212+
</svg>
213+
Low
214+
</span>
215+
),
216+
},
217+
]}
218+
/>
219+
)),
220+
)
170221
.add(
171222
'With disableFilter props',
172223
withInfo({

packages/mcs-lite-ui/src/InputSelect/InputSelect.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import {
2323
FakeInputValue,
2424
TextOverflow,
2525
} from './styled-components';
26-
import { type Value, type ItemProps } from './type.flow';
26+
import { type Value, type ItemProps, type ItemValueMapper } from './type.flow';
2727
import {
28-
filterByChildren,
28+
filterBy,
2929
getInputValue,
3030
getPlaceholder,
3131
getMenuItemHeight,
@@ -41,10 +41,13 @@ type Props = {
4141
focus?: boolean,
4242
noRowsRenderer?: ({ onClose: () => void }) => React.Node,
4343
disableFilter?: boolean,
44+
itemValueMapper?: ItemValueMapper,
4445
// Note: innerRef for the problem of outside click in dialog
4546
menuRef?: (ref: React.ElementRef<typeof StyledMenu>) => void,
4647
};
4748

49+
const defaultItemValueMapper = (item: ItemProps) => item.children;
50+
4851
class PureInputSelect extends React.Component<
4952
Props & { theme: Object },
5053
{
@@ -57,6 +60,7 @@ class PureInputSelect extends React.Component<
5760
kind: 'primary',
5861
noRowsRenderer: () => <NoRowWrapper>No results found</NoRowWrapper>,
5962
disableFilter: false,
63+
itemValueMapper: defaultItemValueMapper,
6064
};
6165
state = { isOpen: false, filter: '', menuWidth: 0 };
6266
componentDidMount() {
@@ -108,10 +112,15 @@ class PureInputSelect extends React.Component<
108112
index: number,
109113
style: Object,
110114
}): React.Element<typeof MenuItem> => {
111-
const { items, value, onChange } = this.props;
115+
const {
116+
items,
117+
value,
118+
onChange,
119+
itemValueMapper = defaultItemValueMapper,
120+
} = this.props;
112121
const { filter } = this.state;
113122
const { onClose } = this;
114-
const filteredItems = filterByChildren(items, filter);
123+
const filteredItems = filterBy({ items, filter, itemValueMapper });
115124
const { value: itemValue, children }: ItemProps = filteredItems[index];
116125
const onItemClick = () => {
117126
onChange(itemValue);
@@ -144,6 +153,7 @@ class PureInputSelect extends React.Component<
144153
focus,
145154
menuRef,
146155
disableFilter,
156+
itemValueMapper = defaultItemValueMapper,
147157
...otherProps
148158
} = this.props;
149159
const { menuWidth, isOpen, filter } = this.state;
@@ -158,7 +168,7 @@ class PureInputSelect extends React.Component<
158168
onClickOutside,
159169
} = this;
160170

161-
const filteredItems = filterByChildren(items, filter);
171+
const filteredItems = filterBy({ items, filter, itemValueMapper });
162172
const activeIndex = R.findIndex(R.propEq('value', value))(items);
163173
const activeItem = items[activeIndex];
164174
const menuItemHeight = getMenuItemHeight(theme);
@@ -175,7 +185,12 @@ class PureInputSelect extends React.Component<
175185
ref={onInputRef}
176186
kind={kind}
177187
focus={focus || isOpen}
178-
value={getInputValue({ isOpen, filter, activeItem })}
188+
value={getInputValue({
189+
isOpen,
190+
filter,
191+
activeItem,
192+
itemValueMapper,
193+
})}
179194
onChange={onFilterChange}
180195
placeholder={getPlaceholder({ isOpen, activeItem, placeholder })}
181196
readOnly={disableFilter}
@@ -186,7 +201,9 @@ class PureInputSelect extends React.Component<
186201
/>
187202
{isOpen &&
188203
activeItem &&
189-
!filter && <FakeInputValue defaultValue={activeItem.children} />}
204+
!filter && (
205+
<FakeInputValue defaultValue={itemValueMapper(activeItem)} />
206+
)}
190207
<StyledButton
191208
kind={kind}
192209
active={focus || isOpen}
@@ -239,15 +256,17 @@ InputSelect.propTypes = {
239256
onChange: PropTypes.func.isRequired, // (value: Value) => Promise<void> | void,
240257
items: PropTypes.arrayOf(
241258
PropTypes.shape({
242-
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
243-
children: PropTypes.string,
259+
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
260+
.isRequired,
261+
children: PropTypes.node.isRequired,
244262
}),
245263
).isRequired,
246264
kind: PropTypes.string,
247265
placeholder: PropTypes.string,
248266
noRowsRenderer: PropTypes.func,
249267
focus: PropTypes.bool,
250268
disableFilter: PropTypes.bool,
269+
itemValueMapper: PropTypes.func,
251270
menuRef: PropTypes.func,
252271
};
253272

packages/mcs-lite-ui/src/InputSelect/__snapshots__/InputSelect.example.storyshot

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,167 @@ exports[`Storyshots InputSelect With autoFocus props 1`] = `
661661
</div>
662662
`;
663663

664+
exports[`Storyshots InputSelect With children React element and itemValueMapper 1`] = `
665+
.c3 {
666+
border-width: 1px;
667+
border-style: solid;
668+
outline: none;
669+
box-sizing: border-box;
670+
-webkit-transition: background-color cubic-bezier(0.47,0,0.75,0.72) 0.3s;
671+
transition: background-color cubic-bezier(0.47,0,0.75,0.72) 0.3s;
672+
line-height: 0;
673+
cursor: pointer;
674+
background-color: #00A1DE;
675+
color: #FFFFFF;
676+
border-radius: 3px;
677+
width: 32px;
678+
min-width: initial;
679+
height: 32px;
680+
padding: 0;
681+
font-size: 1rem;
682+
border-color: rgb(0,130,179);
683+
font-size: 18px;
684+
color: #FFFFFF;
685+
}
686+
687+
.c3:hover {
688+
background-color: rgb(0,151,208);
689+
}
690+
691+
.c3:active {
692+
background-color: rgb(0,144,198);
693+
}
694+
695+
.c2 {
696+
box-sizing: border-box;
697+
width: 100%;
698+
border-width: 1px;
699+
border-style: solid;
700+
border-radius: 3px;
701+
outline: 0;
702+
padding: 0 10px;
703+
line-height: 1;
704+
min-height: 32px;
705+
color: #353630;
706+
font-size: 1rem;
707+
border-color: #D1D2D3;
708+
box-shadow: none;
709+
}
710+
711+
.c2:focus {
712+
border-color: #00A1DE;
713+
box-shadow: 0 0 3px 0 rgba(0,161,222,0.5);
714+
}
715+
716+
.c2::-webkit-input-placeholder {
717+
opacity: 1;
718+
color: #D1D2D3;
719+
}
720+
721+
.c2::-moz-placeholder {
722+
opacity: 1;
723+
color: #D1D2D3;
724+
}
725+
726+
.c2:-ms-input-placeholder {
727+
opacity: 1;
728+
color: #D1D2D3;
729+
}
730+
731+
.c2::placeholder {
732+
opacity: 1;
733+
color: #D1D2D3;
734+
}
735+
736+
.c4 {
737+
line-height: 0;
738+
}
739+
740+
.c4 > * {
741+
-webkit-transform-origin: center;
742+
-ms-transform-origin: center;
743+
transform-origin: center;
744+
-webkit-transition: -webkit-transform 0.4s cubic-bezier(0.68,-0.55,0.27,1.55);
745+
-webkit-transition: transform 0.4s cubic-bezier(0.68,-0.55,0.27,1.55);
746+
transition: transform 0.4s cubic-bezier(0.68,-0.55,0.27,1.55);
747+
-webkit-transform: initial;
748+
-ms-transform: initial;
749+
transform: initial;
750+
}
751+
752+
.c0 {
753+
position: relative;
754+
}
755+
756+
.c1 {
757+
display: -webkit-box;
758+
display: -webkit-flex;
759+
display: -ms-flexbox;
760+
display: flex;
761+
}
762+
763+
.c1 > button {
764+
-webkit-flex-shrink: 0;
765+
-ms-flex-negative: 0;
766+
flex-shrink: 0;
767+
}
768+
769+
.c1 > *:first-child {
770+
border-top-right-radius: 0;
771+
border-bottom-right-radius: 0;
772+
}
773+
774+
.c1 > *:not(:first-child):not(:last-child) {
775+
border-top-left-radius: 0;
776+
border-bottom-left-radius: 0;
777+
border-top-right-radius: 0;
778+
border-bottom-right-radius: 0;
779+
}
780+
781+
.c1 > *:last-child {
782+
border-top-left-radius: 0;
783+
border-bottom-left-radius: 0;
784+
}
785+
786+
<div>
787+
<div
788+
className="c0 c1"
789+
>
790+
<input
791+
autoComplete="off"
792+
className="c2"
793+
kind="primary"
794+
onChange={[Function]}
795+
onClick={[Function]}
796+
onFocus={[Function]}
797+
placeholder=""
798+
readOnly={false}
799+
value="Medium"
800+
/>
801+
<button
802+
className="c3"
803+
onClick={[Function]}
804+
>
805+
<div
806+
className="c4"
807+
>
808+
<svg
809+
fill="currentColor"
810+
height="1em"
811+
preserveAspectRatio="xMidYMid meet"
812+
viewBox="0 0 24 24"
813+
width="1em"
814+
>
815+
<path
816+
d="M7.4 9.8l4.6 4.6 4.6-4.6 1.4 1.4-6 6-6-6 1.4-1.4z"
817+
/>
818+
</svg>
819+
</div>
820+
</button>
821+
</div>
822+
</div>
823+
`;
824+
664825
exports[`Storyshots InputSelect With disableFilter props 1`] = `
665826
.c3 {
666827
border-width: 1px;
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// @flow
2+
import * as React from 'react';
3+
24
export type Value = number | string;
35
export type ItemProps = {
46
value: Value,
5-
children: string,
7+
children: React.Node,
68
};
9+
10+
export type ItemValueMapper = ItemProps => string;

packages/mcs-lite-ui/src/InputSelect/utils.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
11
// @flow
22
import * as R from 'ramda';
3-
import { type ItemProps } from './type.flow';
3+
import { type ItemProps, type ItemValueMapper } from './type.flow';
44

55
export const MAX_HEIGHT = 155;
66

7-
export function filterByChildren(
7+
export function filterBy({
8+
items,
9+
filter,
10+
itemValueMapper,
11+
}: {
812
items: Array<ItemProps>,
913
filter: string,
10-
): Array<ItemProps> {
14+
itemValueMapper: ItemValueMapper,
15+
}): Array<ItemProps> {
1116
const regex = new RegExp(filter, 'gi');
12-
return items.filter(({ children }: ItemProps) => regex.test(children));
17+
return items.filter((item: ItemProps) => regex.test(itemValueMapper(item)));
1318
}
1419

1520
export function getInputValue({
1621
isOpen,
1722
filter,
1823
activeItem,
24+
itemValueMapper,
1925
}: {
2026
isOpen: boolean,
2127
filter: string,
2228
activeItem?: ItemProps,
29+
itemValueMapper: ItemValueMapper,
2330
}): string {
2431
let value;
2532

2633
if (isOpen) {
2734
value = filter;
2835
} else {
29-
value = activeItem ? activeItem.children : '';
36+
value = activeItem ? itemValueMapper(activeItem) : '';
3037
}
3138

3239
return value;

0 commit comments

Comments
 (0)