Compare commits
No commits in common. "dev" and "2.0.0-beta.19" have entirely different histories.
dev
...
2.0.0-beta
|
@ -383,17 +383,17 @@ const factory = (Chip, Input) => {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
placeholder, allowClear, className, clearTooltip, disabled,
|
placeholder, allowClear, className, clearTooltip, disabled,
|
||||||
error, label, value, selectedPosition, style, theme, multiple
|
error, label, value, selectedPosition, theme, multiple
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const inputProps = this.props.inputProps || {};
|
const inputProps = this.props.inputProps || {};
|
||||||
const outerClassName = classnames(theme.autocomplete, {
|
const outerClassName = classnames(theme.autocomplete, {
|
||||||
[theme.focus]: this.state.focus,
|
[theme.focus]: this.state.focus,
|
||||||
}, className);
|
}, className);
|
||||||
const withClear = allowClear && !disabled && (multiple
|
const withClear = allowClear && (multiple
|
||||||
? value && Object.keys(value).length > 0
|
? value && Object.keys(value).length > 0
|
||||||
: value != null);
|
: value != null);
|
||||||
return (
|
return (
|
||||||
<div data-react-toolbox="autocomplete" className={outerClassName} style={style}>
|
<div data-react-toolbox="autocomplete" className={outerClassName}>
|
||||||
{selectedPosition === 'above' ? this.renderSelected() : null}
|
{selectedPosition === 'above' ? this.renderSelected() : null}
|
||||||
{withClear ? <span
|
{withClear ? <span
|
||||||
className={'material-icons '+theme.clear}
|
className={'material-icons '+theme.clear}
|
||||||
|
|
|
@ -21,11 +21,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.withclear input {
|
.withclear input {
|
||||||
text-indent: 28px;
|
text-indent: 28px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.withclear label {
|
.withclear label {
|
||||||
transition-property: top, left, font-size, color;
|
transition-property: top, left, font-size, color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputFilled {
|
.inputFilled {
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.withclear input:not(:focus):not(.inputFilled) ~ label {
|
.withclear input:not(:focus):not(.inputFilled) ~ label {
|
||||||
left: 28px;
|
left: 28px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.values {
|
.values {
|
||||||
|
|
|
@ -69,7 +69,6 @@ const factory = (Check) => {
|
||||||
className={className}
|
className={className}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
style={style}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
{...others}
|
{...others}
|
||||||
|
@ -86,6 +85,7 @@ 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}
|
||||||
|
|
|
@ -20,8 +20,6 @@ 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
|
||||||
|
@ -35,7 +33,7 @@ const factory = (Overlay, Button) => {
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
themeNamespace="overlay"
|
themeNamespace="overlay"
|
||||||
/>
|
/>
|
||||||
<div data-react-toolbox="dialog" className={className} style={style}>
|
<div data-react-toolbox="dialog" className={className}>
|
||||||
<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}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
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;
|
|
@ -0,0 +1,247 @@
|
||||||
|
/* 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 };
|
|
@ -0,0 +1,10 @@
|
||||||
|
: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));
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { Dropdown } from './Dropdown';
|
||||||
|
|
||||||
|
export { DropdownProps, DropdownTheme } from './Dropdown';
|
||||||
|
export { Dropdown }
|
||||||
|
export default Dropdown;
|
|
@ -1,26 +1,11 @@
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import { themr } from 'react-css-themr';
|
import { themr } from 'react-css-themr';
|
||||||
import { autocompleteFactory } from '../autocomplete';
|
import { DROPDOWN } from '../identifiers';
|
||||||
import { Chip } from '../chip';
|
import { dropdownFactory } from './Dropdown';
|
||||||
import { Input } from '../input';
|
import { Input } from '../input';
|
||||||
|
import theme from './theme.css';
|
||||||
|
|
||||||
import theme from '../autocomplete/theme.css';
|
const Dropdown = dropdownFactory(Input);
|
||||||
import overrides from './overrides.css';
|
const ThemedDropdown = themr(DROPDOWN, theme)(Dropdown);
|
||||||
|
|
||||||
const overriddenTheme = { ...theme, inputInputElement: theme.inputInputElement+' '+overrides.inputInputElement };
|
export default ThemedDropdown;
|
||||||
|
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;
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
.inputInputElement {
|
|
||||||
caret-color: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
# 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.|
|
|
@ -0,0 +1,180 @@
|
||||||
|
@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;
|
||||||
|
display: block;
|
||||||
|
left: -4px;
|
||||||
|
padding: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
|
@ -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, style, ...others } = this.props; // eslint-disable-line
|
theme, type, value, onKeyPress, rows = 1, ...others } = this.props;
|
||||||
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} style={style}>
|
<div data-react-toolbox="input" className={className}>
|
||||||
{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} />
|
||||||
|
|
|
@ -58,13 +58,12 @@ 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} style={style}>
|
<label data-react-toolbox="switch" className={_className}>
|
||||||
<input
|
<input
|
||||||
{...others}
|
{...others}
|
||||||
checked={this.props.checked}
|
checked={this.props.checked}
|
||||||
|
|
|
@ -47,44 +47,39 @@
|
||||||
&.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; }
|
||||||
|
|
||||||
&.checkboxCell {
|
|
||||||
padding-right: 5px;
|
|
||||||
width: calc(1.8 * var(--unit));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* styles for both header and normal body cells */
|
.table > thead > tr > td,
|
||||||
.table > tbody > tr > th,
|
|
||||||
.table > tbody > tr > td,
|
.table > tbody > tr > td,
|
||||||
.rowCell {
|
.rowCell {
|
||||||
|
border-bottom: var(--table-dividers);
|
||||||
border-top: var(--table-dividers);
|
border-top: var(--table-dividers);
|
||||||
|
height: var(--table-row-height);
|
||||||
padding-top: var(--table-cell-top);
|
padding-top: var(--table-cell-top);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
&.checkboxCell {
|
&.checkboxCell {
|
||||||
|
padding-right: 5px;
|
||||||
|
width: calc(1.8 * var(--unit));
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
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 {
|
||||||
|
@ -93,17 +88,14 @@
|
||||||
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));
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* styles for all cells in header */
|
|
||||||
.table > thead > tr > th,
|
|
||||||
.table > thead > tr > td,
|
|
||||||
.headCell {
|
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
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,6 @@ 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,
|
tooltipOnFocus: PropTypes.bool,
|
||||||
tooltipPosition: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])),
|
tooltipPosition: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])),
|
||||||
|
@ -76,7 +75,6 @@ const tooltipFactory = (options = {}) => {
|
||||||
visible: false,
|
visible: false,
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
tooltip: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -195,36 +193,6 @@ 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();
|
||||||
|
@ -248,7 +216,6 @@ 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
|
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
|
||||||
|
@ -271,9 +238,6 @@ const tooltipFactory = (options = {}) => {
|
||||||
if (tooltipOnFocus) {
|
if (tooltipOnFocus) {
|
||||||
childProps.onFocus = this.handleMouseEnter;
|
childProps.onFocus = this.handleMouseEnter;
|
||||||
childProps.onBlur = this.handleMouseLeave;
|
childProps.onBlur = this.handleMouseLeave;
|
||||||
} else if (tooltipForChildren) {
|
|
||||||
childProps.onMouseOver = this.handleMouseEnterForChildren;
|
|
||||||
childProps.onMouseOut = this.handleMouseLeaveForChildren;
|
|
||||||
} else {
|
} else {
|
||||||
childProps.onMouseEnter = this.handleMouseEnter;
|
childProps.onMouseEnter = this.handleMouseEnter;
|
||||||
childProps.onMouseLeave = this.handleMouseLeave;
|
childProps.onMouseLeave = this.handleMouseLeave;
|
||||||
|
@ -282,9 +246,8 @@ const tooltipFactory = (options = {}) => {
|
||||||
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.Fragment>
|
return React.createElement(ComposedComponent, finalProps, children,
|
||||||
{React.createElement(ComposedComponent, finalProps, children)}
|
visible && (
|
||||||
{visible && (
|
|
||||||
<Portal>
|
<Portal>
|
||||||
<span
|
<span
|
||||||
ref={this.setTooltipNode}
|
ref={this.setTooltipNode}
|
||||||
|
@ -292,13 +255,11 @@ const tooltipFactory = (options = {}) => {
|
||||||
data-react-toolbox="tooltip"
|
data-react-toolbox="tooltip"
|
||||||
style={active ? { top, left, transform } : { top: '-1000px', left: 0 }}
|
style={active ? { top, left, transform } : { top: '-1000px', left: 0 }}
|
||||||
>
|
>
|
||||||
<span className={theme.tooltipInner}>
|
<span className={theme.tooltipInner}>{tooltip}</span>
|
||||||
{this.state.tooltip || this.props.tooltip}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
),
|
||||||
</React.Fragment>);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.24",
|
"version": "2.0.0-beta.19",
|
||||||
"main": "./lib",
|
"main": "./lib",
|
||||||
"module": "./components",
|
"module": "./components",
|
||||||
"author": {
|
"author": {
|
||||||
|
|
Loading…
Reference in New Issue