Skip to content

Commit fdde12c

Browse files
authored
Merge pull request #653 from data-driven-forms/use-pf4-select-component
Use pf4 select component
2 parents 7ca7b86 + e7c047e commit fdde12c

File tree

8 files changed

+136
-75
lines changed

8 files changed

+136
-75
lines changed

packages/pf4-component-mapper/demo/demo-schemas/select-schema.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,6 @@ const selectSchema = {
164164
};
165165

166166
export default {
167-
...selectSchema
167+
...selectSchema,
168+
fields: [selectSchema.fields[0]]
168169
};

packages/pf4-component-mapper/demo/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const fieldArrayState = { schema: arraySchemaDDF, additionalOptions: {
2424
class App extends React.Component {
2525
constructor(props) {
2626
super(props);
27-
this.state = {schema: wizardSchema, additionalOptions: {}}
27+
this.state = {schema: selectSchema, additionalOptions: {}}
2828
}
2929

3030
render() {

packages/pf4-component-mapper/src/common/select/input.js

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
1-
import React, { Fragment } from 'react';
1+
import React from 'react';
22
import PropTypes from 'prop-types';
3-
import { Divider } from '@patternfly/react-core';
43

54
import './input.scss';
65

76
const Input = ({ inputRef, isSearchable, isDisabled, getInputProps, value, ...props }) => {
87
const inputProps = getInputProps({ disabled: isDisabled });
98
return (
10-
<Fragment>
11-
<div className="pf-c-select__menu-search">
12-
<input
13-
autoFocus
14-
value=""
15-
{...props}
16-
{...(!isSearchable && { tabIndex: '-1' })}
17-
className="pf-c-form-control pf-m-search"
18-
ref={inputRef}
19-
{...{
20-
...inputProps,
21-
value: inputProps.value || '',
22-
onChange: inputProps.onChange || Function
23-
}}
24-
/>
25-
</div>
26-
<Divider />
27-
</Fragment>
9+
<input
10+
value=""
11+
{...props}
12+
className="pf-c-form-control pf-c-select__toggle-typeahead"
13+
ref={inputRef}
14+
{...{
15+
...inputProps,
16+
value: inputProps.value || '',
17+
onChange: inputProps.onChange || Function
18+
}}
19+
/>
2820
);
2921
};
3022

packages/pf4-component-mapper/src/common/select/menu.js

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useEffect, useState, Fragment } from 'react';
1+
import React, { useEffect, useState, useRef } from 'react';
22
import { createPortal } from 'react-dom';
33
import Option from './option';
4-
import Input from './input';
54
import EmptyOption from './empty-options';
65

6+
import './menu.scss';
7+
78
const getScrollParent = (element) => {
89
let style = getComputedStyle(element);
910
const excludeStaticParent = style.position === 'absolute';
@@ -36,31 +37,63 @@ const getMenuPosition = (selectBase) => {
3637
return selectBase.getBoundingClientRect();
3738
};
3839

39-
const MenuPortal = ({ selectToggleRef, menuPortalTarget, children, isSearchable }) => {
40+
const checkScrollVisibility = (scrollableParent, selectRoot, menuRoot) => {
41+
const parentProportions = scrollableParent.getBoundingClientRect();
42+
const rootProportions = selectRoot.getBoundingClientRect();
43+
const menuProportions = menuRoot.getBoundingClientRect();
44+
return {
45+
rootPosition: parentProportions.y,
46+
cropSize: rootProportions.y + rootProportions.height - parentProportions.y,
47+
maxHeight: window.innerHeight - menuProportions.top + 1
48+
};
49+
};
50+
51+
const MenuPortal = ({ selectToggleRef, menuPortalTarget, children }) => {
4052
const [position, setPosition] = useState(getMenuPosition(selectToggleRef.current));
53+
const [{ cropSize, rootPosition, maxHeight }, setCropSize] = useState({});
54+
const menuRef = useRef();
4155
useEffect(() => {
56+
setCropSize({ maxHeight: window.innerHeight - menuRef.current.getBoundingClientRect().top - 4 });
4257
const scrollParentElement = getScrollParent(selectToggleRef.current);
43-
const scrollListener = scrollParentElement.addEventListener('scroll', () => {
58+
const scrollHandler = function() {
59+
setCropSize(checkScrollVisibility(scrollParentElement, selectToggleRef.current, menuRef.current));
4460
setPosition(getMenuPosition(selectToggleRef.current));
45-
});
46-
const resizeListener = window.addEventListener('resize', () => {
61+
};
62+
63+
const resizeHandler = function() {
64+
setCropSize((prevSize) => ({ ...prevSize, maxHeight: window.innerHeight - menuRef.current.getBoundingClientRect().top - 4 }));
4765
setPosition(getMenuPosition(selectToggleRef.current));
48-
});
66+
};
67+
68+
scrollParentElement.addEventListener('scroll', scrollHandler, true);
69+
window.addEventListener('resize', resizeHandler, true);
4970
return () => {
50-
window.removeEventListener('resize', resizeListener);
51-
scrollParentElement.removeEventListener('scroll', scrollListener);
71+
window.removeEventListener('resize', resizeHandler, true);
72+
scrollParentElement.removeEventListener('scroll', scrollHandler, true);
5273
};
5374
}, [selectToggleRef]);
5475

55-
const top = isSearchable ? position.top + position.height + 64 : position.top + position.height;
76+
const top = position.top + position.height;
77+
const sizedMenu = React.cloneElement(children, {
78+
style: {
79+
maxHeight: cropSize < 0 ? maxHeight + cropSize : maxHeight,
80+
overflow: 'auto'
81+
}
82+
});
5683
const portalDiv = (
5784
<div
58-
className={`pf-c-select ddorg_pf4-component-mapper__select-portal-menu${
59-
isSearchable ? ' ddorg_pf4-component-mapper__select-portal-menu-searchable' : ''
60-
}`}
61-
style={{ borderTop: '4px solid white', zIndex: 401, position: 'absolute', top, left: position.left, width: position.width }}
85+
ref={menuRef}
86+
className="pf-c-select ddorg_pf4-component-mapper__select-portal-menu"
87+
style={{
88+
zIndex: 401,
89+
position: 'absolute',
90+
top: cropSize < 0 ? rootPosition : top,
91+
left: position.left,
92+
width: position.width,
93+
overflow: 'hidden'
94+
}}
6295
>
63-
{children}
96+
{cropSize < 0 ? <div style={{ position: 'relative', top: cropSize, width: position.width }}>{sizedMenu}</div> : sizedMenu}
6497
</div>
6598
);
6699

@@ -71,7 +104,6 @@ const Menu = ({
71104
noResultsMessage,
72105
noOptionsMessage,
73106
filterOptions,
74-
inputRef,
75107
isSearchable,
76108
filterValue,
77109
options,
@@ -87,8 +119,7 @@ const Menu = ({
87119
}) => {
88120
const filteredOptions = isSearchable ? filterOptions(options, filterValue) : options;
89121
const menuItems = (
90-
<ul className="pf-c-select__menu">
91-
{!menuIsPortal && isSearchable && <Input inputRef={inputRef} getInputProps={getInputProps} />}
122+
<ul className={`pf-c-select__menu${menuIsPortal ? ' ddorg__pf4-component-mapper__select-menu-portal' : ''}`}>
92123
{filteredOptions.length === 0 && (
93124
<EmptyOption
94125
isSearchable={isSearchable}
@@ -112,16 +143,9 @@ const Menu = ({
112143
);
113144
if (menuIsPortal) {
114145
return (
115-
<Fragment>
116-
{isSearchable && (
117-
<ul className="pf-c-select__menu">
118-
<Input inputRef={inputRef} getInputProps={getInputProps} />
119-
</ul>
120-
)}
121-
<MenuPortal isSearchable={isSearchable} menuPortalTarget={menuPortalTarget} selectToggleRef={selectToggleRef}>
122-
{menuItems}
123-
</MenuPortal>
124-
</Fragment>
146+
<MenuPortal menuPortalTarget={menuPortalTarget} selectToggleRef={selectToggleRef}>
147+
{menuItems}
148+
</MenuPortal>
125149
);
126150
}
127151

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.pf-c-select__menu.ddorg__pf4-component-mapper__select-menu-portal {
2+
margin-top: 2px;
3+
position: relative;
4+
width: calc(100% - 2px);
5+
min-width: calc(100% - 2px);
6+
left: 1px;
7+
border-bottom: 1px solid #ddd;
8+
}

packages/pf4-component-mapper/src/common/select/select-styles.scss

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,11 @@
1212
animation: spin 2s linear infinite;
1313
}
1414

15-
.ddorg_pf4-component-mapper__select-portal-menu.ddorg_pf4-component-mapper__select-portal-menu-searchable {
16-
&::before {
17-
position: absolute;
18-
bottom: -4px;
19-
height: 4px;
20-
left: 0;
21-
right: 0;
22-
background: white;
23-
border-bottom-width: var(--pf-global--BorderWidth--sm);
24-
border-bottom-color: var(--pf-global--BorderColor--dark-100);
25-
border-bottom-style: solid;
26-
border-bottom-width: 1px;
27-
content: "";
28-
}
15+
.pf-c-select_toggle-wrapper.ddorg__pf4-component-mapper__select-toggle-wrapper {
16+
flex: 1;
17+
display: flex;
18+
}
19+
20+
.ddorg__pf4-component-mapper__select-toggle.pf-m-typeahead {
21+
padding-left: 0;
2922
}

packages/pf4-component-mapper/src/common/select/select.js

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const itemToString = (value, isMulti, showMore, handleShowMore, handleChange) =>
2424
if (isMulti) {
2525
const visibleOptions = showMore ? value : value.slice(0, 3);
2626
return (
27-
<div className="pf-c-chip-group" onClick={(event) => event.stopPropagation()}>
27+
<div className="pf-c-chip-group pf-u-ml-sm" onClick={(event) => event.stopPropagation()}>
2828
<ul className="pf-c-chip-group__list" aria-label="Chip group category">
2929
{visibleOptions.map((item, index) => {
3030
const label = typeof item === 'object' ? item.label : item;
@@ -74,17 +74,35 @@ const getValue = (isMulti, option, value) => {
7474
return isSelected ? value.filter(({ value }) => value !== option.value) : [...value, option];
7575
};
7676

77-
const stateReducer = (state, changes, keepMenuOpen) => {
77+
const stateReducer = (state, changes, isMulti) => {
7878
switch (changes.type) {
7979
case Downshift.stateChangeTypes.keyDownEnter:
8080
case Downshift.stateChangeTypes.clickItem:
8181
return {
8282
...changes,
83-
isOpen: keepMenuOpen ? state.isOpen : !state.isOpen,
83+
isOpen: isMulti ? state.isOpen : !state.isOpen,
8484
highlightedIndex: state.highlightedIndex,
85-
inputValue: state.inputValue // prevent filter value change after option click
85+
inputValue: isMulti ? state.inputValue : changes.inputValue // prevent filter value change after option click
8686
};
8787
case Downshift.stateChangeTypes.controlledPropUpdatedSelectedItem:
88+
return {
89+
...changes,
90+
inputValue: state.inputValue
91+
};
92+
case Downshift.stateChangeTypes.mouseUp:
93+
if (typeof changes.inputValue === 'string') {
94+
return {
95+
...changes
96+
};
97+
}
98+
99+
if (Array.isArray(changes.inputValue) && typeof changes.inputValue[0] === 'string') {
100+
return {
101+
...changes,
102+
inputValue: changes.inputValue[0]
103+
};
104+
}
105+
88106
return {
89107
...changes,
90108
inputValue: state.inputValue
@@ -139,11 +157,20 @@ const InternalSelect = ({
139157
<div
140158
ref={selectToggleRef}
141159
disabled={isDisabled}
142-
className={`pf-c-select__toggle${isDisabled ? ' pf-m-disabled' : ''}`}
160+
className={`pf-c-select__toggle${isDisabled ? ' pf-m-disabled' : ''}${
161+
isSearchable ? ' pf-m-typeahead' : ''
162+
} ddorg__pf4-component-mapper__select-toggle`}
143163
{...toggleButtonProps}
144164
>
145165
<div className="pf-c-select_toggle-wrapper ddorg__pf4-component-mapper__select-toggle-wrapper">
146-
<ValueContainer placeholder={placeholder} value={itemToString(selectedItem, isMulti, showMore, handleShowMore, handleChange)} />
166+
<ValueContainer
167+
isMulti={isMulti}
168+
isSearchable={isSearchable}
169+
placeholder={placeholder}
170+
inputRef={inputRef}
171+
getInputProps={getInputProps}
172+
value={itemToString(selectedItem, isMulti, showMore, handleShowMore, handleChange)}
173+
/>
147174
</div>
148175
{isClearable && parsedValue && <ClearIndicator clearSelection={clearSelection} />}
149176
<span className="pf-c-select__toggle-arrow">
@@ -155,9 +182,7 @@ const InternalSelect = ({
155182
noResultsMessage={noResultsMessage}
156183
noOptionsMessage={noOptionsMessage}
157184
isFetching={isFetching}
158-
inputRef={inputRef}
159185
isDisabled={isDisabled}
160-
placeholder={placeholder}
161186
isSearchable={isSearchable}
162187
getInputProps={getInputProps}
163188
filterOptions={filterOptions}
Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1-
import React from 'react';
1+
import React, { Fragment } from 'react';
22
import PropTypes from 'prop-types';
3+
import Input from './input';
4+
5+
const ValueContainer = ({ value, isMulti, placeholder, getInputProps, isSearchable, inputRef }) => {
6+
if (isMulti && isSearchable) {
7+
return (
8+
<Fragment>
9+
{value}
10+
<Input placeholder={placeholder} inputRef={inputRef} getInputProps={getInputProps} />
11+
</Fragment>
12+
);
13+
}
14+
15+
if (!isMulti && isSearchable) {
16+
return <Input placeholder={placeholder} inputRef={inputRef} getInputProps={getInputProps} />;
17+
}
318

4-
const ValueContainer = ({ value, placeholder }) => {
519
return <span className="pf-c-select__toggle-text">{value || placeholder}</span>;
620
};
721

822
ValueContainer.propTypes = {
923
value: PropTypes.node,
10-
placeholder: PropTypes.node
24+
placeholder: PropTypes.node,
25+
isMulti: PropTypes.bool,
26+
getInputProps: PropTypes.func.isRequired,
27+
isSearchable: PropTypes.bool,
28+
inputRef: PropTypes.object
1129
};
1230

1331
export default ValueContainer;

0 commit comments

Comments
 (0)