mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2025-07-06 18:00:35 +02:00

Also: - Move generic Toggle to componentfolder root - Remove dead styles - Refactor some componets
293 lines
8.7 KiB
JavaScript
293 lines
8.7 KiB
JavaScript
/* eslint-disable jsx-a11y/click-events-have-key-events, no-sequences */
|
|
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import { intlShape } from 'react-intl';
|
|
import Icon from '../../Icon';
|
|
|
|
const roundToOneDecimal = number => {
|
|
const rounded = Math.round(number * 10) / 10;
|
|
return rounded.toFixed(1).replace('.', ',');
|
|
};
|
|
|
|
/**
|
|
* Builds an array of options: least, less, default, more, most with preset
|
|
* multipliers or given values for each option. Note: a higher value (relative to
|
|
* the given value) means less/worse.
|
|
*
|
|
* @param {*} options The options to select from.
|
|
*/
|
|
export const getFiveStepOptions = options => [
|
|
{
|
|
title: 'option-least',
|
|
value: options.least || options[0],
|
|
kmhValue: `${roundToOneDecimal(options[0] * 3.6)} km/h`,
|
|
},
|
|
{
|
|
title: 'option-less',
|
|
value: options.less || options[1],
|
|
kmhValue: `${roundToOneDecimal(options[1] * 3.6)} km/h`,
|
|
},
|
|
{
|
|
title: 'option-default',
|
|
value: options[2],
|
|
kmhValue: `${roundToOneDecimal(options[2] * 3.6)} km/h`,
|
|
},
|
|
{
|
|
title: 'option-more',
|
|
value: options.more || options[3],
|
|
kmhValue: `${roundToOneDecimal(options[3] * 3.6)} km/h`,
|
|
},
|
|
{
|
|
title: 'option-most',
|
|
value: options.most || options[4],
|
|
kmhValue: `${roundToOneDecimal(options[4] * 3.6)} km/h`,
|
|
},
|
|
];
|
|
|
|
export const getFiveStepOptionsNumerical = options => {
|
|
const numericalOptions = [];
|
|
options.forEach(item => {
|
|
numericalOptions.push({
|
|
title: `${Math.round(item * 3.6)} km/h`,
|
|
value: item,
|
|
});
|
|
});
|
|
return numericalOptions;
|
|
};
|
|
|
|
/**
|
|
* Represents the types of acceptable values.
|
|
*/
|
|
export const valueShape = PropTypes.oneOfType([
|
|
PropTypes.string,
|
|
PropTypes.number,
|
|
// eslint-disable-next-line
|
|
PropTypes.object,
|
|
]);
|
|
|
|
class SearchSettingsDropdown extends React.Component {
|
|
static propTypes = {
|
|
labelText: PropTypes.string.isRequired,
|
|
options: PropTypes.arrayOf(valueShape).isRequired,
|
|
displayValueFormatter: PropTypes.func,
|
|
currentSelection: PropTypes.shape({
|
|
title: PropTypes.string,
|
|
value: valueShape,
|
|
}).isRequired,
|
|
highlightDefaultValue: PropTypes.bool,
|
|
defaultValue: valueShape,
|
|
displayPattern: PropTypes.string,
|
|
onOptionSelected: PropTypes.func.isRequired,
|
|
formatOptions: PropTypes.bool,
|
|
name: PropTypes.string.isRequired,
|
|
translateLabels: PropTypes.bool,
|
|
// eslint-disable-next-line
|
|
overrideStyle: PropTypes.object,
|
|
};
|
|
|
|
static defaultProps = {
|
|
displayValueFormatter: undefined,
|
|
highlightDefaultValue: false,
|
|
displayPattern: undefined,
|
|
defaultValue: undefined,
|
|
formatOptions: false,
|
|
translateLabels: true,
|
|
overrideStyle: {},
|
|
};
|
|
|
|
static contextTypes = {
|
|
intl: intlShape.isRequired,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = { showDropdown: false };
|
|
this.labelRef = React.createRef();
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
if (this.state.showDropdown) {
|
|
this.labelRef.current.scrollIntoView({ block: 'nearest' });
|
|
}
|
|
}
|
|
|
|
toggleDropdown = prevState => {
|
|
this.setState({
|
|
showDropdown: !prevState,
|
|
});
|
|
};
|
|
|
|
handleDropdownClick = prevState => {
|
|
this.toggleDropdown(prevState);
|
|
};
|
|
|
|
handleChangeOnly = value => {
|
|
this.props.onOptionSelected(value);
|
|
};
|
|
|
|
getOptionTags = (dropdownOptions, prevState) => {
|
|
return dropdownOptions.map(option => (
|
|
<li key={option.displayName + option.value}>
|
|
<label
|
|
className={`settings-dropdown-choice ${
|
|
option.value === this.props.currentSelection.value ? 'selected' : ''
|
|
}`}
|
|
htmlFor={`dropdown-${this.props.name}-${option.value}`}
|
|
>
|
|
<span>
|
|
{option.displayNameObject
|
|
? option.displayNameObject
|
|
: option.displayName}
|
|
</span>
|
|
<span className="right-side">
|
|
<span className="kmh-value">{option.kmhValue}</span>
|
|
<span className="checkmark">
|
|
|
|
{option.value === this.props.currentSelection.value && (
|
|
<Icon
|
|
className="selected-checkmark"
|
|
img="icon-icon_check"
|
|
viewBox="0 0 15 11"
|
|
/>
|
|
)}
|
|
</span>
|
|
<input
|
|
id={`dropdown-${this.props.name}-${option.value}`}
|
|
type="radio"
|
|
name={this.props.name}
|
|
checked={option.value === this.props.currentSelection.value}
|
|
value={option.value}
|
|
onChange={e => {
|
|
this.handleChangeOnly(option.value);
|
|
// try to detect if event is from an actual click or keyboard navigation
|
|
if (e.nativeEvent.clientX || e.nativeEvent.clientY) {
|
|
this.handleDropdownClick(prevState);
|
|
}
|
|
}}
|
|
/>
|
|
</span>
|
|
</label>
|
|
</li>
|
|
));
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
labelText,
|
|
currentSelection,
|
|
options,
|
|
displayValueFormatter,
|
|
highlightDefaultValue,
|
|
defaultValue,
|
|
formatOptions,
|
|
translateLabels,
|
|
overrideStyle,
|
|
} = this.props;
|
|
const { intl } = this.context;
|
|
const { showDropdown } = this.state || {};
|
|
|
|
function applyDefaultValueIdentifier(value, str) {
|
|
return highlightDefaultValue && value === defaultValue
|
|
? `${intl.formatMessage({
|
|
id: 'option-default',
|
|
})} (${str})`
|
|
: str;
|
|
}
|
|
|
|
function getFormattedValue(value) {
|
|
return displayValueFormatter ? displayValueFormatter(value) : value;
|
|
}
|
|
const selectOptions = formatOptions
|
|
? options.map(o =>
|
|
o.title && o.value
|
|
? {
|
|
displayName: `${o.title}_${o.value}`,
|
|
displayNameObject: applyDefaultValueIdentifier(
|
|
o.value,
|
|
translateLabels
|
|
? this.context.intl.formatMessage(
|
|
{ id: o.title },
|
|
{
|
|
title: o.title,
|
|
},
|
|
)
|
|
: o.title,
|
|
),
|
|
value: o.value,
|
|
kmhValue: o.kmhValue || undefined,
|
|
}
|
|
: {
|
|
displayName: `${this.props.displayPattern}_${o}`,
|
|
displayNameObject: applyDefaultValueIdentifier(
|
|
o,
|
|
// eslint-disable-next-line no-nested-ternary
|
|
this.props.displayPattern
|
|
? translateLabels
|
|
? this.context.intl.formatMessage(
|
|
{ id: this.props.displayPattern },
|
|
{
|
|
number: getFormattedValue(o),
|
|
},
|
|
)
|
|
: ({ id: this.props.displayPattern },
|
|
{ number: getFormattedValue(o) })
|
|
: getFormattedValue(o),
|
|
),
|
|
value: o,
|
|
kmhValue: o.kmhValue || undefined,
|
|
},
|
|
)
|
|
: options;
|
|
|
|
return (
|
|
<div
|
|
className="settings-dropdown-wrapper walk-option-inner"
|
|
ref={this.labelRef}
|
|
>
|
|
<button
|
|
type="button"
|
|
className="settings-dropdown-label"
|
|
style={overrideStyle}
|
|
onClick={() => this.toggleDropdown(this.state.showDropdown)}
|
|
>
|
|
<p className="settings-dropdown-label-text">{labelText}</p>
|
|
<span className="settings-dropdown-text-container">
|
|
<p className="settings-dropdown-label-value">
|
|
{/* eslint-disable-next-line no-nested-ternary */}
|
|
{displayValueFormatter
|
|
? displayValueFormatter(currentSelection.title)
|
|
: translateLabels
|
|
? `${intl.formatMessage({
|
|
id: currentSelection.title,
|
|
})}`
|
|
: currentSelection.title}
|
|
</p>
|
|
<span className="sr-only">
|
|
{intl.formatMessage({
|
|
id: showDropdown
|
|
? 'settings-dropdown-close-label'
|
|
: 'settings-dropdown-open-label',
|
|
})}
|
|
</span>
|
|
<Icon
|
|
className={
|
|
this.state.showDropdown
|
|
? 'fake-select-arrow inverted'
|
|
: 'fake-select-arrow'
|
|
}
|
|
img="icon-icon_arrow-dropdown"
|
|
/>
|
|
</span>
|
|
</button>
|
|
{showDropdown && (
|
|
<ul role="radiogroup" className="settings-dropdown">
|
|
{this.getOptionTags(selectOptions, this.state.showDropdown)}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default SearchSettingsDropdown;
|