Compare commits
10 Commits
2.0.0-beta
...
dev
Author | SHA1 | Date |
---|---|---|
Vitaliy Filippov | e745e3332e | |
Vitaliy Filippov | 8690e3a709 | |
Vitaliy Filippov | ba3993c811 | |
Vitaliy Filippov | 0e31a465c7 | |
Vitaliy Filippov | fce0362b5b | |
Vitaliy Filippov | 36fd012442 | |
Vitaliy Filippov | 2427d05568 | |
Vitaliy Filippov | 8f1312f79f | |
Vitaliy Filippov | 3035f7d0ae | |
Vitaliy Filippov | 4f02b11197 |
|
@ -9,6 +9,14 @@
|
||||||
},
|
},
|
||||||
"plugins": ["compat"],
|
"plugins": ["compat"],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-mixed-operators": [
|
||||||
|
"error", { "groups": [
|
||||||
|
["&", "|", "^", "~", "<<", ">>", ">>>"],
|
||||||
|
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
|
||||||
|
["&&", "||"],
|
||||||
|
["in", "instanceof"]
|
||||||
|
] }
|
||||||
|
],
|
||||||
"compat/compat": 2,
|
"compat/compat": 2,
|
||||||
"func-names": "off",
|
"func-names": "off",
|
||||||
"global-require": "off",
|
"global-require": "off",
|
||||||
|
|
|
@ -48,6 +48,7 @@ const factory = (Chip, Input) => {
|
||||||
selectedPosition: PropTypes.oneOf(['above', 'below', 'none']),
|
selectedPosition: PropTypes.oneOf(['above', 'below', 'none']),
|
||||||
source: PropTypes.any,
|
source: PropTypes.any,
|
||||||
minWidth: PropTypes.number,
|
minWidth: PropTypes.number,
|
||||||
|
more: PropTypes.string,
|
||||||
suggestionMatch: PropTypes.oneOf(['disabled', 'start', 'anywhere', 'word', 'none']),
|
suggestionMatch: PropTypes.oneOf(['disabled', 'start', 'anywhere', 'word', 'none']),
|
||||||
theme: PropTypes.shape({
|
theme: PropTypes.shape({
|
||||||
active: PropTypes.string,
|
active: PropTypes.string,
|
||||||
|
@ -61,6 +62,7 @@ const factory = (Chip, Input) => {
|
||||||
values: PropTypes.string,
|
values: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -121,7 +123,7 @@ const factory = (Chip, Input) => {
|
||||||
|
|
||||||
handleQueryChange = (value, event) => {
|
handleQueryChange = (value, event) => {
|
||||||
if (value === '' && !this.props.multiple &&
|
if (value === '' && !this.props.multiple &&
|
||||||
(this.props.allowBlank || this.props.allowClear) && this.props.onChange) {
|
this.props.allowClear && this.props.onChange) {
|
||||||
this.props.onChange(null, event);
|
this.props.onChange(null, event);
|
||||||
}
|
}
|
||||||
this.updateQuery(value);
|
this.updateQuery(value);
|
||||||
|
@ -372,6 +374,7 @@ const factory = (Chip, Input) => {
|
||||||
<ul style={{bottom}}
|
<ul style={{bottom}}
|
||||||
className={theme.suggestions}>
|
className={theme.suggestions}>
|
||||||
{this.renderSuggestions()}
|
{this.renderSuggestions()}
|
||||||
|
{this.props.more ? <li className={theme.suggestion}>{this.props.more}</li> : null}
|
||||||
</ul>
|
</ul>
|
||||||
</Transition>
|
</Transition>
|
||||||
</TransitionGroup>);
|
</TransitionGroup>);
|
||||||
|
@ -379,30 +382,30 @@ const factory = (Chip, Input) => {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
allowClear, clearTooltip, error, label, source, suggestionMatch, query, // eslint-disable-line no-unused-vars
|
placeholder, allowClear, className, clearTooltip, disabled,
|
||||||
selectedPosition, keepFocusOnChange, onQueryChange, // eslint-disable-line no-unused-vars
|
error, label, value, selectedPosition, style, theme, multiple
|
||||||
theme, multiple, minWidth, ...other
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
// outdated properties
|
const inputProps = this.props.inputProps || {};
|
||||||
delete other.showSelectedWhenNotInSource;
|
const outerClassName = classnames(theme.autocomplete, {
|
||||||
delete other.allowCreate;
|
|
||||||
const className = classnames(theme.autocomplete, {
|
|
||||||
[theme.focus]: this.state.focus,
|
[theme.focus]: this.state.focus,
|
||||||
}, this.props.className);
|
}, className);
|
||||||
|
const withClear = allowClear && !disabled && (multiple
|
||||||
const withClear = allowClear && (this.props.multiple ? this.props.value && Object.keys(this.props.value).length > 0 : this.props.value != null);
|
? value && Object.keys(value).length > 0
|
||||||
|
: value != null);
|
||||||
return (
|
return (
|
||||||
<div data-react-toolbox="autocomplete" className={className}>
|
<div data-react-toolbox="autocomplete" className={outerClassName} style={style}>
|
||||||
{selectedPosition === 'above' ? this.renderSelected() : null}
|
{selectedPosition === 'above' ? this.renderSelected() : null}
|
||||||
{withClear ? <span
|
{withClear ? <span
|
||||||
className={'material-icons '+theme.clear}
|
className={'material-icons '+theme.clear}
|
||||||
title={clearTooltip}
|
title={clearTooltip}
|
||||||
onClick={e => this.handleChange(multiple ? [] : null, e)}>clear</span> : null}
|
onClick={e => this.handleChange(multiple ? [] : null, e)}>clear</span> : null}
|
||||||
<Input
|
<Input
|
||||||
{...other}
|
{...inputProps}
|
||||||
ref={(node) => { this.inputNode = node; }}
|
ref={(node) => { this.inputNode = node; }}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={theme.input+(withClear ? ' '+theme.withclear : '')}
|
className={theme.input+(withClear ? ' '+theme.withclear : '')}
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
error={error}
|
error={error}
|
||||||
label={label}
|
label={label}
|
||||||
onBlur={this.handleQueryBlur}
|
onBlur={this.handleQueryBlur}
|
||||||
|
@ -413,15 +416,15 @@ const factory = (Chip, Input) => {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
themeNamespace="input"
|
themeNamespace="input"
|
||||||
value={this.state.query == null
|
value={this.state.query == null
|
||||||
? (this.props.multiple || this.props.value == null
|
? (multiple || value == null
|
||||||
? ''
|
? ''
|
||||||
: this.source().get(''+this.props.value))
|
: this.source().get(''+value))
|
||||||
: this.state.query}
|
: this.state.query}
|
||||||
/>
|
/>
|
||||||
<Portal>
|
<Portal>
|
||||||
{this.state.focus ? this.renderSuggestionList() : null}
|
{this.state.focus ? this.renderSuggestionList() : null}
|
||||||
</Portal>
|
</Portal>
|
||||||
{this.props.selectedPosition === 'below' ? this.renderSelected() : null}
|
{selectedPosition === 'below' ? this.renderSelected() : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,20 +12,20 @@
|
||||||
|
|
||||||
.clear {
|
.clear {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
display: block;
|
||||||
top: 12px;
|
|
||||||
left: -4px;
|
left: -4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.withclear input {
|
.withclear input {
|
||||||
text-indent: 28px !important;
|
text-indent: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.withclear label {
|
.withclear label {
|
||||||
transition-property: top, left, font-size, color !important;
|
transition-property: top, left, font-size, color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputFilled {
|
.inputFilled {
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.withclear input:not(:focus):not(.inputFilled) ~ label {
|
.withclear input:not(:focus):not(.inputFilled) ~ label {
|
||||||
left: 28px !important;
|
left: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.values {
|
.values {
|
||||||
|
@ -49,20 +49,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestions {
|
.suggestions {
|
||||||
position: absolute;
|
|
||||||
background-color: var(--autocomplete-suggestions-background);
|
background-color: var(--autocomplete-suggestions-background);
|
||||||
|
box-shadow: var(--zdepth-shadow-1);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
max-height: 1px;
|
max-height: 1px;
|
||||||
|
-ms-overflow-style: none;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
transition-duration: var(--animation-duration);
|
transition-duration: var(--animation-duration);
|
||||||
transition-property: max-height, box-shadow;
|
transition-property: max-height, box-shadow;
|
||||||
transition-timing-function: var(--animation-curve-default);
|
transition-timing-function: var(--animation-curve-default);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: var(--z-index-highest);
|
z-index: var(--z-index-highest);
|
||||||
box-shadow: var(--zdepth-shadow-1);
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion {
|
.suggestion {
|
||||||
|
|
|
@ -69,6 +69,7 @@ const factory = (Check) => {
|
||||||
className={className}
|
className={className}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
{...others}
|
{...others}
|
||||||
|
@ -85,7 +86,6 @@ const factory = (Check) => {
|
||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
rippleClassName={theme.ripple}
|
rippleClassName={theme.ripple}
|
||||||
style={style}
|
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
{label ? <span data-react-toolbox="label" className={theme.text}>{label}</span> : null}
|
{label ? <span data-react-toolbox="label" className={theme.text}>{label}</span> : null}
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
.chip {
|
.chip {
|
||||||
background-color: var(--chip-background);
|
background-color: var(--chip-background);
|
||||||
border-radius: var(--chip-height);
|
border-radius: var(--chip-height);
|
||||||
min-height: var(--chip-height);
|
|
||||||
color: var(--chip-color);
|
color: var(--chip-color);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: var(--chip-font-size);
|
font-size: var(--chip-font-size);
|
||||||
line-height: var(--chip-height);
|
line-height: var(--chip-height);
|
||||||
margin-right: var(--chip-margin-right);
|
margin-right: var(--chip-margin-right);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
min-height: var(--chip-height);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 var(--chip-padding);
|
padding: 0 var(--chip-padding);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -20,6 +20,8 @@ const factory = (Overlay, Button) => {
|
||||||
[props.theme.active]: props.active,
|
[props.theme.active]: props.active,
|
||||||
}, props.className);
|
}, props.className);
|
||||||
|
|
||||||
|
const style = props.style; // eslint-disable-line
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal className={props.theme.wrapper}>
|
<Portal className={props.theme.wrapper}>
|
||||||
<Overlay
|
<Overlay
|
||||||
|
@ -33,7 +35,7 @@ const factory = (Overlay, Button) => {
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
themeNamespace="overlay"
|
themeNamespace="overlay"
|
||||||
/>
|
/>
|
||||||
<div data-react-toolbox="dialog" className={className}>
|
<div data-react-toolbox="dialog" className={className} style={style}>
|
||||||
<section role="body" className={props.theme.body}>
|
<section role="body" className={props.theme.body}>
|
||||||
{props.title ? <h6 className={props.theme.title}>{props.title}</h6> : null}
|
{props.title ? <h6 className={props.theme.title}>{props.title}</h6> : null}
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import ReactToolbox from "../index";
|
|
||||||
|
|
||||||
export interface DropdownTheme {
|
|
||||||
/**
|
|
||||||
* Added to the root element when the dropdown is active.
|
|
||||||
*/
|
|
||||||
active?: string;
|
|
||||||
/**
|
|
||||||
* Added to the root element when it's disabled.
|
|
||||||
*/
|
|
||||||
disabled?: string;
|
|
||||||
/**
|
|
||||||
* Root element class.
|
|
||||||
*/
|
|
||||||
dropdown?: string;
|
|
||||||
/**
|
|
||||||
* Used for the error element.
|
|
||||||
*/
|
|
||||||
error?: string;
|
|
||||||
/**
|
|
||||||
* Added to the inner wrapper if it's errored.
|
|
||||||
*/
|
|
||||||
errored?: string;
|
|
||||||
/**
|
|
||||||
* Used for the inner wrapper of the component.
|
|
||||||
*/
|
|
||||||
field?: string;
|
|
||||||
/**
|
|
||||||
* Used for the the label element.
|
|
||||||
*/
|
|
||||||
label?: string;
|
|
||||||
/**
|
|
||||||
* Used when dropdown has required attribute.
|
|
||||||
*/
|
|
||||||
required?: string;
|
|
||||||
/**
|
|
||||||
* Used to highlight the selected value.
|
|
||||||
*/
|
|
||||||
selected?: string;
|
|
||||||
/**
|
|
||||||
* Used as a wrapper for the given template value.
|
|
||||||
*/
|
|
||||||
templateValue?: string;
|
|
||||||
/**
|
|
||||||
* Added to the root element when it's opening up.
|
|
||||||
*/
|
|
||||||
up?: string;
|
|
||||||
/**
|
|
||||||
* Used for each value in the dropdown component.
|
|
||||||
*/
|
|
||||||
value?: string;
|
|
||||||
/**
|
|
||||||
* Used for the list of values.
|
|
||||||
*/
|
|
||||||
values?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DropdownProps extends ReactToolbox.Props {
|
|
||||||
/**
|
|
||||||
* If true the dropdown will preselect the first item if the supplied value matches none of the options' values.
|
|
||||||
* @default true
|
|
||||||
*/
|
|
||||||
allowBlank?: boolean;
|
|
||||||
/**
|
|
||||||
* If true, the dropdown will open up or down depending on the position in the screen.
|
|
||||||
* @default true
|
|
||||||
*/
|
|
||||||
auto?: boolean;
|
|
||||||
/**
|
|
||||||
* Set the component as disabled.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
disabled?: boolean;
|
|
||||||
/**
|
|
||||||
* Give an error string to display under the field.
|
|
||||||
*/
|
|
||||||
error?: string;
|
|
||||||
/**
|
|
||||||
* The text string to use for the floating label element.
|
|
||||||
*/
|
|
||||||
label?: string;
|
|
||||||
/**
|
|
||||||
* Used for setting the label from source
|
|
||||||
*/
|
|
||||||
labelKey?: string;
|
|
||||||
/**
|
|
||||||
* Name for the input field.
|
|
||||||
*/
|
|
||||||
name?: string;
|
|
||||||
/**
|
|
||||||
* Callback function that is fired when the component is blurred.
|
|
||||||
*/
|
|
||||||
onBlur?: Function;
|
|
||||||
/**
|
|
||||||
* Callback function that is fired when the component's value changes.
|
|
||||||
*/
|
|
||||||
onChange?: Function;
|
|
||||||
/**
|
|
||||||
* Callback function that is fired when the component is focused.
|
|
||||||
*/
|
|
||||||
onFocus?: Function;
|
|
||||||
/**
|
|
||||||
* If true, the dropdown has a required attribute.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
required?: boolean;
|
|
||||||
/**
|
|
||||||
* Array of data objects with the data to represent in the dropdown.
|
|
||||||
*/
|
|
||||||
source: any[];
|
|
||||||
/**
|
|
||||||
* Callback function that returns a JSX template to represent the element.
|
|
||||||
*/
|
|
||||||
template?: Function;
|
|
||||||
/**
|
|
||||||
* Classnames object defining the component style.
|
|
||||||
*/
|
|
||||||
theme?: DropdownTheme;
|
|
||||||
/**
|
|
||||||
* Default value using JSON data.
|
|
||||||
*/
|
|
||||||
value?: string | number;
|
|
||||||
/**
|
|
||||||
* Used for setting the value from source
|
|
||||||
*/
|
|
||||||
valueKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Dropdown extends React.Component<DropdownProps, {}> { }
|
|
||||||
|
|
||||||
export default Dropdown;
|
|
|
@ -1,247 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { themr } from 'react-css-themr';
|
|
||||||
import { DROPDOWN } from '../identifiers';
|
|
||||||
import InjectInput from '../input/Input';
|
|
||||||
import events from '../utils/events';
|
|
||||||
|
|
||||||
const factory = (Input) => {
|
|
||||||
class Dropdown extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
allowBlank: PropTypes.bool,
|
|
||||||
allowClear: PropTypes.bool,
|
|
||||||
clearTooltip: PropTypes.string,
|
|
||||||
auto: PropTypes.bool,
|
|
||||||
className: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
error: PropTypes.string,
|
|
||||||
label: PropTypes.string,
|
|
||||||
labelKey: PropTypes.string,
|
|
||||||
name: PropTypes.string,
|
|
||||||
onBlur: PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
onFocus: PropTypes.func,
|
|
||||||
required: PropTypes.bool,
|
|
||||||
source: PropTypes.arrayOf(PropTypes.oneOfType([
|
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.object,
|
|
||||||
])).isRequired,
|
|
||||||
template: PropTypes.func,
|
|
||||||
theme: PropTypes.shape({
|
|
||||||
active: PropTypes.string,
|
|
||||||
disabled: PropTypes.string,
|
|
||||||
dropdown: PropTypes.string,
|
|
||||||
error: PropTypes.string,
|
|
||||||
errored: PropTypes.string,
|
|
||||||
field: PropTypes.string,
|
|
||||||
label: PropTypes.string,
|
|
||||||
required: PropTypes.string,
|
|
||||||
selected: PropTypes.string,
|
|
||||||
templateValue: PropTypes.string,
|
|
||||||
up: PropTypes.string,
|
|
||||||
value: PropTypes.string,
|
|
||||||
values: PropTypes.string,
|
|
||||||
}),
|
|
||||||
value: PropTypes.oneOfType([
|
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number,
|
|
||||||
]),
|
|
||||||
valueKey: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
auto: true,
|
|
||||||
className: '',
|
|
||||||
allowBlank: true,
|
|
||||||
allowClear: false,
|
|
||||||
clearTooltip: 'Clear',
|
|
||||||
disabled: false,
|
|
||||||
labelKey: 'label',
|
|
||||||
required: false,
|
|
||||||
valueKey: 'value',
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
active: false,
|
|
||||||
up: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
|
||||||
if (!this.state.active && nextState.active) {
|
|
||||||
events.addEventsToDocument(this.getDocumentEvents());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
if (prevState.active && !this.state.active) {
|
|
||||||
events.removeEventsFromDocument(this.getDocumentEvents());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.state.active) {
|
|
||||||
events.removeEventsFromDocument(this.getDocumentEvents());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDocumentEvents = () => ({
|
|
||||||
click: this.handleDocumentClick,
|
|
||||||
touchend: this.handleDocumentClick,
|
|
||||||
});
|
|
||||||
|
|
||||||
getSelectedItem = () => {
|
|
||||||
for (const item of this.props.source) {
|
|
||||||
if (item[this.props.valueKey] === this.props.value) return item;
|
|
||||||
}
|
|
||||||
return !this.props.allowBlank
|
|
||||||
? this.props.source[0]
|
|
||||||
: undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSelect = (item, event) => {
|
|
||||||
if (!this.props.disabled && this.props.onChange) {
|
|
||||||
if (this.props.name) event.target.name = this.props.name;
|
|
||||||
this.props.onChange(item, event);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = (event) => {
|
|
||||||
this.open(event);
|
|
||||||
events.pauseEvent(event);
|
|
||||||
if (this.props.onClick) this.props.onClick(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDocumentClick = (event) => {
|
|
||||||
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
|
|
||||||
this.setState({ active: false });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
close = () => {
|
|
||||||
if (this.state.active) {
|
|
||||||
this.setState({ active: false });
|
|
||||||
if (this.props.onBlur) this.props.onBlur(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open = (event) => {
|
|
||||||
if (this.state.active) return;
|
|
||||||
const client = event.target.getBoundingClientRect();
|
|
||||||
const screenHeight = window.innerHeight || document.documentElement.offsetHeight;
|
|
||||||
const up = this.props.auto ? client.top > ((screenHeight / 2) + client.height) : false;
|
|
||||||
this.setState({ active: true, up });
|
|
||||||
if (this.props.onFocus) this.props.onFocus(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFocus = (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
if (!this.props.disabled) this.open(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBlur = (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
if (this.state.active) this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplateValue(selected) {
|
|
||||||
const { theme } = this.props;
|
|
||||||
const className = classnames(theme.field, {
|
|
||||||
[theme.errored]: this.props.error,
|
|
||||||
[theme.disabled]: this.props.disabled,
|
|
||||||
[theme.required]: this.props.required,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className} onClick={this.handleClick}>
|
|
||||||
<div className={`${theme.templateValue} ${theme.value}`}>
|
|
||||||
{this.props.template(selected)}
|
|
||||||
</div>
|
|
||||||
{this.props.label
|
|
||||||
? (
|
|
||||||
<label className={theme.label}>
|
|
||||||
{this.props.label}
|
|
||||||
{this.props.required ? <span className={theme.required}> * </span> : null}
|
|
||||||
</label>
|
|
||||||
) : null}
|
|
||||||
{this.props.error ? <span className={theme.error}>{this.props.error}</span> : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderValue = (item, idx) => {
|
|
||||||
const { labelKey, theme, valueKey } = this.props;
|
|
||||||
const className = classnames({
|
|
||||||
[theme.selected]: item[valueKey] === this.props.value,
|
|
||||||
[theme.disabled]: item.disabled,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={idx}
|
|
||||||
className={className}
|
|
||||||
onMouseDown={!item.disabled && this.handleSelect.bind(this, item[valueKey])}
|
|
||||||
>
|
|
||||||
{this.props.template ? this.props.template(item) : item[labelKey]}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
allowBlank, allowClear, clearTooltip, auto, labelKey, required, onChange, onFocus, onBlur, // eslint-disable-line no-unused-vars
|
|
||||||
source, template, theme, valueKey, ...others
|
|
||||||
} = this.props;
|
|
||||||
const selected = this.getSelectedItem();
|
|
||||||
const className = classnames(theme.dropdown, {
|
|
||||||
[theme.up]: this.state.up,
|
|
||||||
[theme.active]: this.state.active,
|
|
||||||
[theme.disabled]: this.props.disabled,
|
|
||||||
[theme.required]: this.props.required,
|
|
||||||
[theme.withclear]: allowClear && selected,
|
|
||||||
}, this.props.className);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={className}
|
|
||||||
style={{outline: 'none'}}
|
|
||||||
data-react-toolbox="dropdown"
|
|
||||||
tabIndex="-1"
|
|
||||||
>
|
|
||||||
{allowClear && selected ? <span
|
|
||||||
className={'material-icons '+theme.clear}
|
|
||||||
title={clearTooltip}
|
|
||||||
onClick={(e) => this.props.onChange(null, e)}>clear</span> : null}
|
|
||||||
<Input
|
|
||||||
{...others}
|
|
||||||
tabIndex="0"
|
|
||||||
className={theme.value}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
onBlur={this.handleBlur}
|
|
||||||
onFocus={this.handleFocus}
|
|
||||||
required={this.props.required}
|
|
||||||
readOnly
|
|
||||||
type={template && selected ? 'hidden' : null}
|
|
||||||
theme={theme}
|
|
||||||
themeNamespace="input"
|
|
||||||
value={selected && selected[labelKey] ? selected[labelKey] : ''}
|
|
||||||
/>
|
|
||||||
{template && selected ? this.renderTemplateValue(selected) : null}
|
|
||||||
<ul className={theme.values} style={this.state.up ? {bottom: '100%'} : {top: '100%'}}>
|
|
||||||
{source.map(this.renderValue)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Dropdown;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dropdown = factory(InjectInput);
|
|
||||||
export default themr(DROPDOWN)(Dropdown);
|
|
||||||
export { factory as dropdownFactory };
|
|
||||||
export { Dropdown };
|
|
|
@ -1,10 +0,0 @@
|
||||||
:root {
|
|
||||||
--dropdown-value-border-size: calc(var(--input-field-height) / 7);
|
|
||||||
--dropdown-color-white: var(--color-white);
|
|
||||||
--dropdown-color-primary: var(--color-primary);
|
|
||||||
--dropdown-color-primary-contrast: var(--color-primary-contrast);
|
|
||||||
--dropdown-color-disabled: color(var(--color-black) a(26%));
|
|
||||||
--dropdown-value-hover-background: var(--palette-grey-200);
|
|
||||||
--dropdown-overflow-max-height: 45vh;
|
|
||||||
--dropdown-value-border-radius: calc(0.2 * var(--unit));
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { Dropdown } from './Dropdown';
|
|
||||||
|
|
||||||
export { DropdownProps, DropdownTheme } from './Dropdown';
|
|
||||||
export { Dropdown }
|
|
||||||
export default Dropdown;
|
|
|
@ -1,11 +1,26 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
import { themr } from 'react-css-themr';
|
import { themr } from 'react-css-themr';
|
||||||
import { DROPDOWN } from '../identifiers';
|
import { autocompleteFactory } from '../autocomplete';
|
||||||
import { dropdownFactory } from './Dropdown';
|
import { Chip } from '../chip';
|
||||||
import { Input } from '../input';
|
import { Input } from '../input';
|
||||||
import theme from './theme.css';
|
|
||||||
|
|
||||||
const Dropdown = dropdownFactory(Input);
|
import theme from '../autocomplete/theme.css';
|
||||||
const ThemedDropdown = themr(DROPDOWN, theme)(Dropdown);
|
import overrides from './overrides.css';
|
||||||
|
|
||||||
export default ThemedDropdown;
|
const overriddenTheme = { ...theme, inputInputElement: theme.inputInputElement+' '+overrides.inputInputElement };
|
||||||
export { ThemedDropdown as Dropdown };
|
|
||||||
|
const Autocomplete = autocompleteFactory(Chip, Input);
|
||||||
|
const ThemedAutocomplete = themr(AUTOCOMPLETE, overriddenTheme, { withRef: true })(Autocomplete);
|
||||||
|
|
||||||
|
export const Dropdown = (props) => <Autocomplete
|
||||||
|
readOnly
|
||||||
|
multiple={false}
|
||||||
|
keepFocusOnChange={false}
|
||||||
|
allowCreate={false}
|
||||||
|
showSelectedWhenNotInSource={false}
|
||||||
|
suggestionMatch="disabled"
|
||||||
|
{...props}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
export default Dropdown;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.inputInputElement {
|
||||||
|
caret-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -1,73 +0,0 @@
|
||||||
# Dropdown
|
|
||||||
|
|
||||||
The Dropdown selects an option between multiple selections. The element displays the current state and a down arrow. When it is clicked, it displays the list of available options.
|
|
||||||
|
|
||||||
<!-- example -->
|
|
||||||
```jsx
|
|
||||||
import Dropdown from 'react-toolbox/lib/dropdown';
|
|
||||||
|
|
||||||
const countries = [
|
|
||||||
{ value: 'EN-gb', label: 'England' },
|
|
||||||
{ value: 'ES-es', label: 'Spain'},
|
|
||||||
{ value: 'TH-th', label: 'Thailand' },
|
|
||||||
{ value: 'EN-en', label: 'USA'}
|
|
||||||
];
|
|
||||||
|
|
||||||
class DropdownTest extends React.Component {
|
|
||||||
state = { value: 'ES-es' };
|
|
||||||
|
|
||||||
handleChange = (value) => {
|
|
||||||
this.setState({value: value});
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
auto
|
|
||||||
onChange={this.handleChange}
|
|
||||||
source={countries}
|
|
||||||
value={this.state.value}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to provide a theme via context, the component key is `RTDropdown`.
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
|:-------------|:-----------|:--------|:------------|
|
|
||||||
| `allowBlank` | `Boolean` | `true` | If false the dropdown will preselect the first item if the supplied value matches none of the options' values.|
|
|
||||||
| `auto` | `Boolean` | `true` | If true, the dropdown will open up or down depending on the position in the screen.|
|
|
||||||
| `className` | `String` | `''` | Set the class to give custom styles to the dropdown.|
|
|
||||||
| `disabled` | `Boolean` | `false` | Set the component as disabled.|
|
|
||||||
| `error` | `String` | | Give an error string to display under the field.|
|
|
||||||
| `label` | `String` | | The text string to use for the floating label element.|
|
|
||||||
| `onBlur` | `Function` | | Callback function that is fired when the component is blurred.|
|
|
||||||
| `onChange` | `Function` | | Callback function that is fired when the component's value changes.|
|
|
||||||
| `onFocus` | `Function` | | Callback function that is fired when the component is focused.|
|
|
||||||
| `source` | `Array` | | Array of data objects with the data to represent in the dropdown.|
|
|
||||||
| `template` | `Function` | | Callback function that returns a JSX template to represent the element.|
|
|
||||||
| `value` | `String` | | Default value using JSON data.|
|
|
||||||
| `required` | `Boolean` | `false` | If true, the dropdown has a required attribute.|
|
|
||||||
|
|
||||||
## Theming
|
|
||||||
|
|
||||||
This component uses an `Input` under the covers. The theme object is passed down namespaced under `input` keyword. This means you can use the same theme classNames from `Input` component but namespaced with `input`. For example, to style the label you have to use `inputLabel` className.
|
|
||||||
|
|
||||||
| Name | Description|
|
|
||||||
|:----------------|:-----------|
|
|
||||||
| `active` | Added to the root element when the dropdown is active.|
|
|
||||||
| `disabled` | Added to the root element when it's disabled.|
|
|
||||||
| `dropdown` | Root element class.|
|
|
||||||
| `error` | Used for the error element.|
|
|
||||||
| `errored` | Added to the inner wrapper if it's errored.|
|
|
||||||
| `field` | Used for the inner wrapper of the component.|
|
|
||||||
| `label` | Used for the the label element.|
|
|
||||||
| `selected` | Used to highlight the selected value.|
|
|
||||||
| `templateValue` | Used as a wrapper for the given template value.|
|
|
||||||
| `up` | Added to the root element when it's opening up.|
|
|
||||||
| `value` | Used for each value in the dropdown component.|
|
|
||||||
| `values` | Used for the list of values.|
|
|
|
@ -1,180 +0,0 @@
|
||||||
@import '../colors.css';
|
|
||||||
@import '../variables.css';
|
|
||||||
@import '../input/config.css';
|
|
||||||
@import './config.css';
|
|
||||||
|
|
||||||
.dropdown {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
@apply --reset;
|
|
||||||
|
|
||||||
&:not(.active) {
|
|
||||||
& > .values {
|
|
||||||
max-height: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
& > .label,
|
|
||||||
& > .value {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .values {
|
|
||||||
box-shadow: var(--zdepth-shadow-1);
|
|
||||||
max-height: var(--dropdown-overflow-max-height);
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.up) > .values {
|
|
||||||
bottom: auto;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.up > .values {
|
|
||||||
bottom: 0;
|
|
||||||
top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
cursor: normal;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.withclear input {
|
|
||||||
text-indent: 28px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
& > input {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
border-left: var(--dropdown-value-border-size) solid transparent;
|
|
||||||
border-right: var(--dropdown-value-border-size) solid transparent;
|
|
||||||
border-top: var(--dropdown-value-border-size) solid var(--input-text-bottom-border-color);
|
|
||||||
content: '';
|
|
||||||
height: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
right: var(--input-chevron-offset);
|
|
||||||
top: 50%;
|
|
||||||
transition: transform var(--animation-duration) var(--animation-curve-default);
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.field {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: var(--input-padding) 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.errored {
|
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
& > .label {
|
|
||||||
color: var(--input-text-error-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .templateValue {
|
|
||||||
border-bottom: 1px solid var(--input-text-error-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .label > .required {
|
|
||||||
color: var(--input-text-error-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
cursor: normal;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
& > .templateValue {
|
|
||||||
border-bottom-style: dotted;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.templateValue {
|
|
||||||
background-color: var(--input-text-background-color);
|
|
||||||
border-bottom: 1px solid var(--input-text-bottom-border-color);
|
|
||||||
color: var(--color-text);
|
|
||||||
min-height: var(--input-field-height);
|
|
||||||
padding: var(--input-field-padding) 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: var(--input-text-label-color);
|
|
||||||
font-size: var(--input-label-font-size);
|
|
||||||
left: 0;
|
|
||||||
line-height: var(--input-field-font-size);
|
|
||||||
position: absolute;
|
|
||||||
top: var(--input-focus-label-top);
|
|
||||||
|
|
||||||
& .required {
|
|
||||||
color: var(--input-text-error-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: var(--input-text-error-color);
|
|
||||||
font-size: var(--input-label-font-size);
|
|
||||||
line-height: var(--input-underline-height);
|
|
||||||
margin-bottom: calc(-1 * var(--input-underline-height));
|
|
||||||
}
|
|
||||||
|
|
||||||
.values {
|
|
||||||
background-color: var(--dropdown-color-white);
|
|
||||||
border-radius: var(--dropdown-value-border-radius);
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0;
|
|
||||||
position: absolute;
|
|
||||||
transition-duration: var(--animation-duration);
|
|
||||||
transition-property: max-height, box-shadow;
|
|
||||||
transition-timing-function: var(--animation-curve-default);
|
|
||||||
width: 100%;
|
|
||||||
z-index: var(--z-index-high);
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: var(--unit);
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover:not(.disabled) {
|
|
||||||
background-color: var(--dropdown-value-hover-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
color: var(--dropdown-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
color: var(--dropdown-color-disabled);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear {
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
|
||||||
top: 12px;
|
|
||||||
left: -4px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
|
@ -163,7 +163,7 @@ const factory = (FontIcon) => {
|
||||||
render() {
|
render() {
|
||||||
const { children, defaultValue, disabled, error, floating, hint, icon,
|
const { children, defaultValue, disabled, error, floating, hint, icon,
|
||||||
name, label: labelText, maxLength, multiline, required, role,
|
name, label: labelText, maxLength, multiline, required, role,
|
||||||
theme, type, value, onKeyPress, rows = 1, ...others } = this.props;
|
theme, type, value, onKeyPress, rows = 1, style, ...others } = this.props; // eslint-disable-line
|
||||||
const length = maxLength && value ? value.length : 0;
|
const length = maxLength && value ? value.length : 0;
|
||||||
const labelClassName = classnames(theme.label, { [theme.fixed]: !floating });
|
const labelClassName = classnames(theme.label, { [theme.fixed]: !floating });
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ const factory = (FontIcon) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-react-toolbox="input" className={className}>
|
<div data-react-toolbox="input" className={className} style={style}>
|
||||||
{React.createElement(multiline ? 'textarea' : 'input', inputElementProps)}
|
{React.createElement(multiline ? 'textarea' : 'input', inputElementProps)}
|
||||||
{icon ? <FontIcon className={theme.icon} value={icon} /> : null}
|
{icon ? <FontIcon className={theme.icon} value={icon} /> : null}
|
||||||
<span className={theme.bar} />
|
<span className={theme.bar} />
|
||||||
|
|
|
@ -66,6 +66,12 @@ const factory = (MenuItem) => {
|
||||||
: this.props.position;
|
: this.props.position;
|
||||||
this.setState({ position, width, height });
|
this.setState({ position, width, height });
|
||||||
});
|
});
|
||||||
|
if (this.state.active) {
|
||||||
|
events.addEventsToDocument({
|
||||||
|
click: this.handleDocumentClick,
|
||||||
|
touchstart: this.handleDocumentClick,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
|
|
@ -58,12 +58,13 @@ const factory = (Thumb) => {
|
||||||
disabled,
|
disabled,
|
||||||
onChange, // eslint-disable-line no-unused-vars
|
onChange, // eslint-disable-line no-unused-vars
|
||||||
ripple,
|
ripple,
|
||||||
|
style, // eslint-disable-line
|
||||||
theme,
|
theme,
|
||||||
...others
|
...others
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const _className = classnames(theme[disabled ? 'disabled' : 'field'], className);
|
const _className = classnames(theme[disabled ? 'disabled' : 'field'], className);
|
||||||
return (
|
return (
|
||||||
<label data-react-toolbox="switch" className={_className}>
|
<label data-react-toolbox="switch" className={_className} style={style}>
|
||||||
<input
|
<input
|
||||||
{...others}
|
{...others}
|
||||||
checked={this.props.checked}
|
checked={this.props.checked}
|
||||||
|
|
|
@ -28,12 +28,14 @@
|
||||||
@apply --reset;
|
@apply --reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table > head, .head {
|
.table > head,
|
||||||
|
.head {
|
||||||
padding-bottom: calc(0.3 * var(--unit));
|
padding-bottom: calc(0.3 * var(--unit));
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table > tbody > tr, .row {
|
.table > tbody > tr,
|
||||||
|
.row {
|
||||||
color: var(--table-row-color);
|
color: var(--table-row-color);
|
||||||
height: var(--table-row-height);
|
height: var(--table-row-height);
|
||||||
transition-duration: 0.28s;
|
transition-duration: 0.28s;
|
||||||
|
@ -45,39 +47,44 @@
|
||||||
&.selected { background-color: var(--table-selection-color); }
|
&.selected { background-color: var(--table-selection-color); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* common styles for all kinds of cells */
|
||||||
.table > thead > tr > th,
|
.table > thead > tr > th,
|
||||||
.table > tbody > tr > th,
|
.table > tbody > tr > th,
|
||||||
.table > thead > tr > td,
|
.table > thead > tr > td,
|
||||||
.table > tbody > tr > td,
|
.table > tbody > tr > td,
|
||||||
.rowCell,
|
.rowCell,
|
||||||
.headCell {
|
.headCell {
|
||||||
|
border-bottom: var(--table-dividers);
|
||||||
|
height: var(--table-row-height);
|
||||||
padding: 0 var(--table-column-padding) 12px var(--table-column-padding);
|
padding: 0 var(--table-column-padding) 12px var(--table-column-padding);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
&:first-of-type { padding-left: 24px; }
|
&:first-of-type { padding-left: 24px; }
|
||||||
&:last-of-type { padding-right: 24px; }
|
&:last-of-type { padding-right: 24px; }
|
||||||
&.numeric { text-align: right; }
|
&.numeric { text-align: right; }
|
||||||
}
|
|
||||||
|
|
||||||
.table > thead > tr > td,
|
|
||||||
.table > tbody > tr > td,
|
|
||||||
.rowCell {
|
|
||||||
border-bottom: var(--table-dividers);
|
|
||||||
border-top: var(--table-dividers);
|
|
||||||
height: var(--table-row-height);
|
|
||||||
padding-top: var(--table-cell-top);
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
&.checkboxCell {
|
&.checkboxCell {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
width: calc(1.8 * var(--unit));
|
width: calc(1.8 * var(--unit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* styles for both header and normal body cells */
|
||||||
|
.table > tbody > tr > th,
|
||||||
|
.table > tbody > tr > td,
|
||||||
|
.rowCell {
|
||||||
|
border-top: var(--table-dividers);
|
||||||
|
padding-top: var(--table-cell-top);
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&.checkboxCell {
|
||||||
& > * {
|
& > * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* styles for header cells both in body and header */
|
||||||
.table > thead > tr > th,
|
.table > thead > tr > th,
|
||||||
.table > tbody > tr > th,
|
.table > tbody > tr > th,
|
||||||
.headCell {
|
.headCell {
|
||||||
|
@ -86,14 +93,17 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
height: var(--table-row-height);
|
height: var(--table-row-height);
|
||||||
line-height: calc(2.4 * var(--unit));
|
line-height: calc(2.4 * var(--unit));
|
||||||
padding-bottom: 8px;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* styles for all cells in header */
|
||||||
|
.table > thead > tr > th,
|
||||||
|
.table > thead > tr > td,
|
||||||
|
.headCell {
|
||||||
|
padding-bottom: 8px;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
|
|
||||||
&.checkboxCell {
|
&.checkboxCell {
|
||||||
padding-right: 5px;
|
|
||||||
width: calc(1.8 * var(--unit));
|
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
margin: 0 0 3px;
|
margin: 0 0 3px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,9 @@ const tooltipFactory = (options = {}) => {
|
||||||
PropTypes.node,
|
PropTypes.node,
|
||||||
]),
|
]),
|
||||||
tooltipDelay: PropTypes.number,
|
tooltipDelay: PropTypes.number,
|
||||||
|
tooltipForChildren: PropTypes.bool,
|
||||||
tooltipHideOnClick: PropTypes.bool,
|
tooltipHideOnClick: PropTypes.bool,
|
||||||
|
tooltipOnFocus: PropTypes.bool,
|
||||||
tooltipPosition: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])),
|
tooltipPosition: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])),
|
||||||
tooltipShowOnClick: PropTypes.bool,
|
tooltipShowOnClick: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -72,6 +74,9 @@ const tooltipFactory = (options = {}) => {
|
||||||
active: false,
|
active: false,
|
||||||
position: this.props.tooltipPosition,
|
position: this.props.tooltipPosition,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
tooltip: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -88,15 +93,44 @@ const tooltipFactory = (options = {}) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getPosition(element) {
|
setTooltipNode = (node) => {
|
||||||
|
this.tooltipNode = node;
|
||||||
|
if (node) {
|
||||||
|
const { width: vw, height: vh } = getViewport();
|
||||||
|
const { top, left, position } = this.state;
|
||||||
|
const width = this.tooltipNode.offsetWidth;
|
||||||
|
const height = this.tooltipNode.offsetHeight;
|
||||||
|
let x = -50;
|
||||||
|
let y = -50;
|
||||||
|
if (position === POSITION.TOP || position === POSITION.BOTTOM) {
|
||||||
|
y = position === POSITION.TOP ? -100 : 0;
|
||||||
|
if (left + width / 2 > vw) {
|
||||||
|
x = -Math.ceil(100 * (left + width - vw + 1) / width);
|
||||||
|
} else if (left - width / 2 < 0) {
|
||||||
|
x = Math.ceil(100 * (width - left) / width);
|
||||||
|
}
|
||||||
|
} else if (position === POSITION.LEFT || position === POSITION.RIGHT) {
|
||||||
|
x = position === POSITION.LEFT ? -100 : 0;
|
||||||
|
if (top + height / 2 > vh) {
|
||||||
|
y = -Math.ceil(100 * (top + height - vh + 1) / height);
|
||||||
|
} else if (top - height / 2 < 0) {
|
||||||
|
y = Math.ceil(100 * (height - top) / height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({ transform: `scale(1) translateX(${x}%) translateY(${y}%)` });
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.setState({ active: true });
|
||||||
|
}, this.props.tooltipDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosition(origin) {
|
||||||
const { tooltipPosition } = this.props;
|
const { tooltipPosition } = this.props;
|
||||||
if (tooltipPosition === POSITION.HORIZONTAL) {
|
if (tooltipPosition === POSITION.HORIZONTAL) {
|
||||||
const origin = element.getBoundingClientRect();
|
|
||||||
const { width: ww } = getViewport();
|
const { width: ww } = getViewport();
|
||||||
const toRight = origin.left < ((ww / 2) - (origin.width / 2));
|
const toRight = origin.left < ((ww / 2) - (origin.width / 2));
|
||||||
return toRight ? POSITION.RIGHT : POSITION.LEFT;
|
return toRight ? POSITION.RIGHT : POSITION.LEFT;
|
||||||
} else if (tooltipPosition === POSITION.VERTICAL) {
|
} else if (tooltipPosition === POSITION.VERTICAL) {
|
||||||
const origin = element.getBoundingClientRect();
|
|
||||||
const { height: wh } = getViewport();
|
const { height: wh } = getViewport();
|
||||||
const toBottom = origin.top < ((wh / 2) - (origin.height / 2));
|
const toBottom = origin.top < ((wh / 2) - (origin.height / 2));
|
||||||
return toBottom ? POSITION.BOTTOM : POSITION.TOP;
|
return toBottom ? POSITION.BOTTOM : POSITION.TOP;
|
||||||
|
@ -106,10 +140,7 @@ const tooltipFactory = (options = {}) => {
|
||||||
|
|
||||||
activate({ top, left, position }) {
|
activate({ top, left, position }) {
|
||||||
if (this.timeout) clearTimeout(this.timeout);
|
if (this.timeout) clearTimeout(this.timeout);
|
||||||
this.setState({ visible: true, position });
|
this.setState({ active: false, visible: true, position, top, left });
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.setState({ active: true, top, left });
|
|
||||||
}, this.props.tooltipDelay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate() {
|
deactivate() {
|
||||||
|
@ -123,32 +154,31 @@ const tooltipFactory = (options = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatePosition(element) {
|
calculatePosition(element) {
|
||||||
const position = this.getPosition(element);
|
const origin = element.getBoundingClientRect();
|
||||||
const { top, left, height, width } = element.getBoundingClientRect();
|
const position = this.getPosition(origin);
|
||||||
const xOffset = window.scrollX || window.pageXOffset;
|
const { top, left, height, width } = origin;
|
||||||
const yOffset = window.scrollY || window.pageYOffset;
|
|
||||||
if (position === POSITION.BOTTOM) {
|
if (position === POSITION.BOTTOM) {
|
||||||
return {
|
return {
|
||||||
top: top + height + yOffset,
|
top: top + height,
|
||||||
left: left + (width / 2) + xOffset,
|
left: left + (width / 2),
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
} else if (position === POSITION.TOP) {
|
} else if (position === POSITION.TOP) {
|
||||||
return {
|
return {
|
||||||
top: top + yOffset,
|
top,
|
||||||
left: left + (width / 2) + xOffset,
|
left: left + (width / 2),
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
} else if (position === POSITION.LEFT) {
|
} else if (position === POSITION.LEFT) {
|
||||||
return {
|
return {
|
||||||
top: top + (height / 2) + yOffset,
|
top: top + (height / 2),
|
||||||
left: left + xOffset,
|
left,
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
} else if (position === POSITION.RIGHT) {
|
} else if (position === POSITION.RIGHT) {
|
||||||
return {
|
return {
|
||||||
top: top + (height / 2) + yOffset,
|
top: top + (height / 2),
|
||||||
left: left + width + xOffset,
|
left: left + width,
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -165,6 +195,36 @@ const tooltipFactory = (options = {}) => {
|
||||||
if (this.props.onMouseLeave) this.props.onMouseLeave(event);
|
if (this.props.onMouseLeave) this.props.onMouseLeave(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleMouseEnterForChildren = (event) => {
|
||||||
|
let el = event.target;
|
||||||
|
while (el && (!el.getAttribute || !el.getAttribute('tooltip'))) {
|
||||||
|
el = el.parentNode;
|
||||||
|
}
|
||||||
|
if (el) {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
this.timeout = null;
|
||||||
|
}
|
||||||
|
this.setState({ tooltip: el.getAttribute('tooltip') });
|
||||||
|
const pos = this.calculatePosition(el);
|
||||||
|
if (!this.state.visible) {
|
||||||
|
this.activate(pos);
|
||||||
|
} else if (this.state.position !== pos.position ||
|
||||||
|
this.state.top !== pos.top || this.state.left !== pos.left) {
|
||||||
|
this.setState({ active: false, visible: false }, () => this.activate(pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseLeaveForChildren = () => {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.deactivate();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
handleClick = (event) => {
|
handleClick = (event) => {
|
||||||
if (this.props.tooltipHideOnClick && this.state.active) {
|
if (this.props.tooltipHideOnClick && this.state.active) {
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
|
@ -178,7 +238,7 @@ const tooltipFactory = (options = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { active, left, top, position, visible } = this.state;
|
const { active, left, top, transform, position, visible } = this.state;
|
||||||
const positionClass = `tooltip${position.charAt(0).toUpperCase() + position.slice(1)}`;
|
const positionClass = `tooltip${position.charAt(0).toUpperCase() + position.slice(1)}`;
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
|
@ -188,6 +248,8 @@ const tooltipFactory = (options = {}) => {
|
||||||
onMouseEnter, // eslint-disable-line no-unused-vars
|
onMouseEnter, // eslint-disable-line no-unused-vars
|
||||||
onMouseLeave, // eslint-disable-line no-unused-vars
|
onMouseLeave, // eslint-disable-line no-unused-vars
|
||||||
tooltip,
|
tooltip,
|
||||||
|
tooltipForChildren,
|
||||||
|
tooltipOnFocus, // eslint-disable-line no-unused-vars
|
||||||
tooltipDelay, // eslint-disable-line no-unused-vars
|
tooltipDelay, // eslint-disable-line no-unused-vars
|
||||||
tooltipHideOnClick, // eslint-disable-line no-unused-vars
|
tooltipHideOnClick, // eslint-disable-line no-unused-vars
|
||||||
tooltipPosition, // eslint-disable-line no-unused-vars
|
tooltipPosition, // eslint-disable-line no-unused-vars
|
||||||
|
@ -204,27 +266,39 @@ const tooltipFactory = (options = {}) => {
|
||||||
...other,
|
...other,
|
||||||
className,
|
className,
|
||||||
onClick: this.handleClick,
|
onClick: this.handleClick,
|
||||||
onMouseEnter: this.handleMouseEnter,
|
|
||||||
onMouseLeave: this.handleMouseLeave,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (tooltipOnFocus) {
|
||||||
|
childProps.onFocus = this.handleMouseEnter;
|
||||||
|
childProps.onBlur = this.handleMouseLeave;
|
||||||
|
} else if (tooltipForChildren) {
|
||||||
|
childProps.onMouseOver = this.handleMouseEnterForChildren;
|
||||||
|
childProps.onMouseOut = this.handleMouseLeaveForChildren;
|
||||||
|
} else {
|
||||||
|
childProps.onMouseEnter = this.handleMouseEnter;
|
||||||
|
childProps.onMouseLeave = this.handleMouseLeave;
|
||||||
|
}
|
||||||
|
|
||||||
const shouldPass = typeof ComposedComponent !== 'string' && defaultPassthrough;
|
const shouldPass = typeof ComposedComponent !== 'string' && defaultPassthrough;
|
||||||
const finalProps = shouldPass ? { ...childProps, theme } : childProps;
|
const finalProps = shouldPass ? { ...childProps, theme } : childProps;
|
||||||
|
|
||||||
return React.createElement(ComposedComponent, finalProps, children,
|
return (<React.Fragment>
|
||||||
visible && (
|
{React.createElement(ComposedComponent, finalProps, children)}
|
||||||
|
{visible && (
|
||||||
<Portal>
|
<Portal>
|
||||||
<span
|
<span
|
||||||
ref={(node) => { this.tooltipNode = node; }}
|
ref={this.setTooltipNode}
|
||||||
className={_className}
|
className={_className}
|
||||||
data-react-toolbox="tooltip"
|
data-react-toolbox="tooltip"
|
||||||
style={{ top, left }}
|
style={active ? { top, left, transform } : { top: '-1000px', left: 0 }}
|
||||||
>
|
>
|
||||||
<span className={theme.tooltipInner}>{tooltip}</span>
|
<span className={theme.tooltipInner}>
|
||||||
|
{this.state.tooltip || this.props.tooltip}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Portal>
|
</Portal>
|
||||||
),
|
)}
|
||||||
);
|
</React.Fragment>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
:root {
|
:root {
|
||||||
--tooltip-background: color(rgb(97, 97, 97) a(90%));
|
--tooltip-background: color(rgb(97, 97, 97) a(90%));
|
||||||
--tooltip-margin: calc(0.5 * var(--unit));
|
--tooltip-margin: calc(0.5 * var(--unit));
|
||||||
--tooltip-border-radius: calc(0.2 * var(--unit));
|
--tooltip-border-radius: calc(0.5 * var(--unit));
|
||||||
--tooltip-color: var(--color-white);
|
--tooltip-color: var(--color-white);
|
||||||
--tooltip-font-size: var(--unit);
|
--tooltip-font-size: calc(1.5 * var(--unit));
|
||||||
--tooltip-max-width: calc(17 * var(--unit));
|
--tooltip-max-width: calc(35 * var(--unit));
|
||||||
--tooltip-padding: calc(0.8 * var(--unit));
|
--tooltip-padding: calc(1.2 * var(--unit));
|
||||||
--tooltip-animation-duration: 200ms;
|
--tooltip-animation-duration: 200ms;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
font-family: var(--preferred-font);
|
font-family: var(--preferred-font);
|
||||||
font-size: var(--tooltip-font-size);
|
font-size: var(--tooltip-font-size);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
line-height: var(--font-size-small);
|
line-height: calc(1.1 * var(--tooltip-font-size));
|
||||||
max-width: var(--tooltip-max-width);
|
max-width: var(--tooltip-max-width);
|
||||||
padding: var(--tooltip-margin);
|
padding: var(--tooltip-margin);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: fixed;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
transform: scale(0) translateX(-50%);
|
transform: scale(0) translateX(-50%);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "react-toolbox",
|
"name": "react-toolbox",
|
||||||
"description": "A set of React components implementing Google's Material Design specification with the power of CSS Modules.",
|
"description": "A set of React components implementing Google's Material Design specification with the power of CSS Modules.",
|
||||||
"homepage": "http://www.react-toolbox.io",
|
"homepage": "http://www.react-toolbox.io",
|
||||||
"version": "2.0.0-beta.15",
|
"version": "2.0.0-beta.24",
|
||||||
"main": "./lib",
|
"main": "./lib",
|
||||||
"module": "./components",
|
"module": "./components",
|
||||||
"author": {
|
"author": {
|
||||||
|
@ -12,11 +12,11 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/react-toolbox/react-toolbox.git"
|
"url": "git+https://github.com/vitalif/react-toolbox.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"email": "issues@react-toolbox.io",
|
"email": "issues@react-toolbox.io",
|
||||||
"url": "https://github.com/react-toolbox/react-toolbox/issues"
|
"url": "https://github.com/vitalif/react-toolbox/issues"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"components",
|
"components",
|
||||||
|
|
Loading…
Reference in New Issue