Update react-tether package

This commit is contained in:
Mark McDowall 2019-03-05 19:38:39 -08:00
parent de7e805718
commit e7bfea8c69
7 changed files with 360 additions and 280 deletions

View File

@ -34,6 +34,8 @@ class ImportSeriesSelectSeries extends Component {
super(props, context); super(props, context);
this._seriesLookupTimeout = null; this._seriesLookupTimeout = null;
this._buttonRef = {};
this._contentRef = {};
this.state = { this.state = {
term: props.id, term: props.id,
@ -44,14 +46,6 @@ class ImportSeriesSelectSeries extends Component {
// //
// Control // Control
_setButtonRef = (ref) => {
this._buttonRef = ref;
}
_setContentRef = (ref) => {
this._contentRef = ref;
}
_addListener() { _addListener() {
window.addEventListener('click', this.onWindowClick); window.addEventListener('click', this.onWindowClick);
} }
@ -64,14 +58,18 @@ class ImportSeriesSelectSeries extends Component {
// Listeners // Listeners
onWindowClick = (event) => { onWindowClick = (event) => {
const button = ReactDOM.findDOMNode(this._buttonRef); const button = ReactDOM.findDOMNode(this._buttonRef.current);
const content = ReactDOM.findDOMNode(this._contentRef); const content = ReactDOM.findDOMNode(this._contentRef.current);
if (!button) { if (!button || !content) {
return; return;
} }
if (!button.contains(event.target) && content && !content.contains(event.target) && this.state.isOpen) { if (
!button.contains(event.target) &&
!content.contains(event.target) &&
this.state.isOpen
) {
this.setState({ isOpen: false }); this.setState({ isOpen: false });
this._removeListener(); this._removeListener();
} }
@ -134,124 +132,145 @@ class ImportSeriesSelectSeries extends Component {
element: styles.tether element: styles.tether
}} }}
{...tetherOptions} {...tetherOptions}
> renderTarget={
<Link (ref) => {
ref={this._setButtonRef} this._buttonRef = ref;
className={styles.button}
component="div"
onPress={this.onPress}
>
{
isLookingUpSeries && isQueued && !isPopulated &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
{ return (
isPopulated && selectedSeries && isExistingSeries && <div ref={ref}>
<Icon <Link
className={styles.warningIcon} className={styles.button}
name={icons.WARNING} component="div"
kind={kinds.WARNING} onPress={this.onPress}
/> >
} {
isLookingUpSeries && isQueued && !isPopulated ?
<LoadingIndicator
className={styles.loading}
size={20}
/> :
null
}
{ {
isPopulated && selectedSeries && isPopulated && selectedSeries && isExistingSeries ?
<ImportSeriesTitle <Icon
title={selectedSeries.title} className={styles.warningIcon}
year={selectedSeries.year} name={icons.WARNING}
network={selectedSeries.network} kind={kinds.WARNING}
isExistingSeries={isExistingSeries} /> :
/> null
} }
{ {
isPopulated && !selectedSeries && isPopulated && selectedSeries ?
<div className={styles.noMatches}> <ImportSeriesTitle
<Icon title={selectedSeries.title}
className={styles.warningIcon} year={selectedSeries.year}
name={icons.WARNING} network={selectedSeries.network}
kind={kinds.WARNING} isExistingSeries={isExistingSeries}
/> /> :
null
}
{
isPopulated && !selectedSeries ?
<div className={styles.noMatches}>
<Icon
className={styles.warningIcon}
name={icons.WARNING}
kind={kinds.WARNING}
/>
No match found! No match found!
</div> </div> :
} null
}
{ {
!isFetching && !!error && !isFetching && !!error ?
<div> <div>
<Icon <Icon
className={styles.warningIcon} className={styles.warningIcon}
title={errorMessage} title={errorMessage}
name={icons.WARNING} name={icons.WARNING}
kind={kinds.WARNING} kind={kinds.WARNING}
/> />
Search failed, please try again later. Search failed, please try again later.
</div> :
null
}
<div className={styles.dropdownArrowContainer}>
<Icon
name={icons.CARET_DOWN}
/>
</div>
</Link>
</div> </div>
);
} }
}
renderElement={
(ref) => {
this._contentRef = ref;
<div className={styles.dropdownArrowContainer}> if (!this.state.isOpen) {
<Icon return;
name={icons.CARET_DOWN} }
/>
</div>
</Link>
{ return (
this.state.isOpen && <div
<div ref={ref}
ref={this._setContentRef} className={styles.contentContainer}
className={styles.contentContainer} >
> <div className={styles.content}>
<div className={styles.content}> <div className={styles.searchContainer}>
<div className={styles.searchContainer}> <div className={styles.searchIconContainer}>
<div className={styles.searchIconContainer}> <Icon name={icons.SEARCH} />
<Icon name={icons.SEARCH} /> </div>
<TextInput
className={styles.searchInput}
name={`${name}_textInput`}
value={this.state.term}
onChange={this.onSearchInputChange}
/>
<FormInputButton
kind={kinds.DEFAULT}
spinnerIcon={icons.REFRESH}
canSpin={true}
isSpinning={isFetching}
onPress={this.onRefreshPress}
>
<Icon name={icons.REFRESH} />
</FormInputButton>
</div> </div>
<TextInput <div className={styles.results}>
className={styles.searchInput} {
name={`${name}_textInput`} items.map((item) => {
value={this.state.term} return (
onChange={this.onSearchInputChange} <ImportSeriesSearchResultConnector
/> key={item.tvdbId}
tvdbId={item.tvdbId}
<FormInputButton title={item.title}
kind={kinds.DEFAULT} year={item.year}
spinnerIcon={icons.REFRESH} network={item.network}
canSpin={true} onPress={this.onSeriesSelect}
isSpinning={isFetching} />
onPress={this.onRefreshPress} );
> })
<Icon name={icons.REFRESH} /> }
</FormInputButton> </div>
</div>
<div className={styles.results}>
{
items.map((item) => {
return (
<ImportSeriesSearchResultConnector
key={item.tvdbId}
tvdbId={item.tvdbId}
title={item.title}
year={item.year}
network={item.network}
onPress={this.onSeriesSelect}
/>
);
})
}
</div> </div>
</div> </div>
</div> );
}
} }
</TetherComponent> />
); );
} }
} }

View File

@ -87,6 +87,9 @@ class EnhancedSelectInput extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this._buttonRef = {};
this._optionsRef = {};
this.state = { this.state = {
isOpen: false, isOpen: false,
selectedIndex: getSelectedIndex(props), selectedIndex: getSelectedIndex(props),
@ -106,14 +109,6 @@ class EnhancedSelectInput extends Component {
// //
// Control // Control
_setButtonRef = (ref) => {
this._buttonRef = ref;
}
_setOptionsRef = (ref) => {
this._optionsRef = ref;
}
_addListener() { _addListener() {
window.addEventListener('click', this.onWindowClick); window.addEventListener('click', this.onWindowClick);
} }
@ -126,8 +121,8 @@ class EnhancedSelectInput extends Component {
// Listeners // Listeners
onWindowClick = (event) => { onWindowClick = (event) => {
const button = ReactDOM.findDOMNode(this._buttonRef); const button = ReactDOM.findDOMNode(this._buttonRef.current);
const options = ReactDOM.findDOMNode(this._optionsRef); const options = ReactDOM.findDOMNode(this._optionsRef.current);
if (!button || this.state.isMobile) { if (!button || this.state.isMobile) {
return; return;
@ -276,75 +271,91 @@ class EnhancedSelectInput extends Component {
element: styles.tether element: styles.tether
}} }}
{...tetherOptions} {...tetherOptions}
> renderTarget={
<Measure (ref) => {
whitelist={['width']} this._buttonRef = ref;
onMeasure={this.onMeasure}
>
<Link
ref={this._setButtonRef}
className={classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning,
isDisabled && disabledClassName
)}
isDisabled={isDisabled}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onPress={this.onPress}
>
<SelectedValueComponent
{...selectedValueOptions}
{...selectedOption}
isDisabled={isDisabled}
>
{selectedOption ? selectedOption.value : null}
</SelectedValueComponent>
<div return (
className={isDisabled ? <Measure
styles.dropdownArrowContainerDisabled : whitelist={['width']}
styles.dropdownArrowContainer onMeasure={this.onMeasure}
} >
> <div ref={ref}>
<Icon <Link
name={icons.CARET_DOWN} className={classNames(
/> className,
</div> hasError && styles.hasError,
</Link> hasWarning && styles.hasWarning,
</Measure> isDisabled && disabledClassName
)}
isDisabled={isDisabled}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onPress={this.onPress}
>
<SelectedValueComponent
{...selectedValueOptions}
{...selectedOption}
isDisabled={isDisabled}
>
{selectedOption ? selectedOption.value : null}
</SelectedValueComponent>
{ <div
isOpen && !isMobile && className={isDisabled ?
<div styles.dropdownArrowContainerDisabled :
ref={this._setOptionsRef} styles.dropdownArrowContainer
className={styles.optionsContainer} }
style={{ >
minWidth: width <Icon
}} name={icons.CARET_DOWN}
> />
<div className={styles.options}> </div>
{ </Link>
values.map((v, index) => { </div>
return ( </Measure>
<OptionComponent );
key={v.key} }
id={v.key}
isSelected={index === selectedIndex}
{...v}
isMobile={false}
onSelect={this.onSelect}
>
{v.value}
</OptionComponent>
);
})
}
</div>
</div>
} }
</TetherComponent> renderElement={
(ref) => {
this._optionsRef = ref;
if (!isOpen || isMobile) {
return;
}
return (
<div
ref={ref}
className={styles.optionsContainer}
style={{
minWidth: width
}}
>
<div className={styles.options}>
{
values.map((v, index) => {
return (
<OptionComponent
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
{...v}
isMobile={false}
onSelect={this.onSelect}
>
{v.value}
</OptionComponent>
);
})
}
</div>
</div>
);
}
}
/>
{ {
isMobile && isMobile &&

View File

@ -38,6 +38,9 @@ class Menu extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this._menuRef = {};
this._menuContentRef = {};
this.state = { this.state = {
isMenuOpen: false, isMenuOpen: false,
maxHeight: 0 maxHeight: 0
@ -60,7 +63,7 @@ class Menu extends Component {
return; return;
} }
const menu = ReactDOM.findDOMNode(this.refs.menu); const menu = ReactDOM.findDOMNode(this._menuRef.current);
if (!menu) { if (!menu) {
return; return;
@ -73,9 +76,13 @@ class Menu extends Component {
} }
setMaxHeight() { setMaxHeight() {
this.setState({ const maxHeight = this.getMaxHeight();
maxHeight: this.getMaxHeight()
}); if (maxHeight !== this.state.maxHeight) {
this.setState({
maxHeight
});
}
} }
_addListener() { _addListener() {
@ -99,10 +106,10 @@ class Menu extends Component {
// Listeners // Listeners
onWindowClick = (event) => { onWindowClick = (event) => {
const menu = ReactDOM.findDOMNode(this.refs.menu); const menu = ReactDOM.findDOMNode(this._menuRef.current);
const menuContent = ReactDOM.findDOMNode(this.refs.menuContent); const menuContent = ReactDOM.findDOMNode(this._menuContentRef.current);
if (!menu) { if (!menu || !menuContent) {
return; return;
} }
@ -116,7 +123,17 @@ class Menu extends Component {
this.setMaxHeight(); this.setMaxHeight();
} }
onWindowScroll = () => { onWindowScroll = (event) => {
if (!this._menuContentRef.current) {
return;
}
const menuContent = ReactDOM.findDOMNode(this._menuContentRef.current);
if (menuContent && menuContent.contains(event.target)) {
return;
}
this.setMaxHeight(); this.setMaxHeight();
} }
@ -158,35 +175,46 @@ class Menu extends Component {
} }
); );
const content = React.cloneElement(
childrenArray[1],
{
ref: 'menuContent',
alignMenu,
maxHeight,
isOpen: isMenuOpen
}
);
return ( return (
<TetherComponent <TetherComponent
classes={{ classes={{
element: styles.tether element: styles.tether
}} }}
{...tetherOptions[alignMenu]} {...tetherOptions[alignMenu]}
> renderTarget={
<div (ref) => {
ref="menu" this._menuRef = ref;
className={className}
>
{button}
</div>
{ return (
isMenuOpen && <div
content ref={ref}
className={className}
>
{button}
</div>
);
}
} }
</TetherComponent> renderElement={
(ref) => {
this._menuContentRef = ref;
if (!isMenuOpen) {
return null;
}
return React.cloneElement(
childrenArray[1],
{
ref,
alignMenu,
maxHeight,
isOpen: isMenuOpen
}
);
}
}
/>
); );
} }
} }

View File

@ -105,42 +105,53 @@ class Popover extends Component {
element: styles.tether element: styles.tether
}} }}
{...tetherOptions[position]} {...tetherOptions[position]}
> renderTarget={
<span (ref) => (
className={className} <span
onClick={this.onClick} ref={ref}
onMouseEnter={this.onMouseEnter} className={className}
onMouseLeave={this.onMouseLeave} onClick={this.onClick}
>
{anchor}
</span>
{
this.state.isOpen &&
<div
className={styles.popoverContainer}
onMouseEnter={this.onMouseEnter} onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave} onMouseLeave={this.onMouseLeave}
> >
<div className={styles.popover}> {anchor}
<div </span>
className={classNames( )
styles.arrow, }
styles[position] renderElement={
)} (ref) => {
/> if (!this.state.isOpen) {
return null;
}
<div className={styles.title}> return (
{title} <div
</div> ref={ref}
className={styles.popoverContainer}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<div className={styles.popover}>
<div
className={classNames(
styles.arrow,
styles[position]
)}
/>
<div className={styles.body}> <div className={styles.title}>
{body} {title}
</div>
<div className={styles.body}>
{body}
</div>
</div> </div>
</div> </div>
</div> );
}
} }
</TetherComponent> />
); );
} }
} }

View File

@ -105,44 +105,55 @@ class Tooltip extends Component {
element: styles.tether element: styles.tether
}} }}
{...tetherOptions[position]} {...tetherOptions[position]}
> renderTarget={
<span (ref) => (
className={className} <span
onClick={this.onClick} ref={ref}
onMouseEnter={this.onMouseEnter} className={className}
onMouseLeave={this.onMouseLeave} onClick={this.onClick}
>
{anchor}
</span>
{
this.state.isOpen &&
<div
className={styles.tooltipContainer}
onMouseEnter={this.onMouseEnter} onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave} onMouseLeave={this.onMouseLeave}
> >
{anchor}
</span>
)
}
renderElement={
(ref) => {
if (!this.state.isOpen) {
return;
}
return (
<div <div
className={classNames( ref={ref}
styles.tooltip, className={styles.tooltipContainer}
styles[kind] onMouseEnter={this.onMouseEnter}
)} onMouseLeave={this.onMouseLeave}
> >
<div <div
className={classNames( className={classNames(
styles.arrow, styles.tooltip,
styles[kind], styles[kind]
styles[position]
)} )}
/> >
<div
className={classNames(
styles.arrow,
styles[kind],
styles[position]
)}
/>
<div className={styles.body}> <div className={styles.body}>
{tooltip} {tooltip}
</div>
</div> </div>
</div> </div>
</div> );
}
} }
</TetherComponent> />
); );
} }
} }

View File

@ -101,7 +101,7 @@
"react-router-dom": "4.3.1", "react-router-dom": "4.3.1",
"react-slider": "0.11.2", "react-slider": "0.11.2",
"react-tabs": "3.0.0", "react-tabs": "3.0.0",
"react-tether": "1.0.1", "react-tether": "2.0.0",
"react-text-truncate": "0.14.0", "react-text-truncate": "0.14.0",
"react-virtualized": "9.21.0", "react-virtualized": "9.21.0",
"redux": "4.0.1", "redux": "4.0.1",

View File

@ -6940,13 +6940,13 @@ react-tabs@3.0.0:
classnames "^2.2.0" classnames "^2.2.0"
prop-types "^15.5.0" prop-types "^15.5.0"
react-tether@1.0.1: react-tether@2.0.0:
version "1.0.1" version "2.0.0"
resolved "https://registry.yarnpkg.com/react-tether/-/react-tether-1.0.1.tgz#6e5173764d4f9b8bef6d1b20ff51972909674942" resolved "https://registry.yarnpkg.com/react-tether/-/react-tether-2.0.0.tgz#84928b9636f1fe0a6874d1e450e7822e87f8cb07"
integrity sha512-OsgGk2hmsIpnpMl1Uq9aYKopG4V7bDuz2msNYPaRosF0CBbpa1YVF9P1qJ8MRsf1Zj/oK/I2uH++S+ffqxL98A== integrity sha512-iJnqTQV42Pf7w4xrg3g1gxSxbBCXleDt8AzlSoAqRINqB+mhcJUeegpB8SFMJ/nKT7lSfMkx3GvUfYY+9sPBGw==
dependencies: dependencies:
prop-types "^15.5.8" prop-types "^15.6.2"
tether "^1.4.3" tether "^1.4.5"
react-text-truncate@0.14.0: react-text-truncate@0.14.0:
version "0.14.0" version "0.14.0"
@ -8243,7 +8243,7 @@ terser@^3.16.1:
source-map "~0.6.1" source-map "~0.6.1"
source-map-support "~0.5.9" source-map-support "~0.5.9"
tether@^1.4.3: tether@^1.4.5:
version "1.4.5" version "1.4.5"
resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.5.tgz#8efd7b35572767ba502259ba9b1cc167fcf6f2c1" resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.5.tgz#8efd7b35572767ba502259ba9b1cc167fcf6f2c1"
integrity sha512-fysT1Gug2wbRi7a6waeu39yVDwiNtvwj5m9eRD+qZDSHKNghLo6KqP/U3yM2ap6TNUL2skjXGJaJJTJqoC31vw== integrity sha512-fysT1Gug2wbRi7a6waeu39yVDwiNtvwj5m9eRD+qZDSHKNghLo6KqP/U3yM2ap6TNUL2skjXGJaJJTJqoC31vw==