[Glitch] Fix dropdown menu positions when scrolling
Port fd33bcb3b2
to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
main
parent
3e63fcd4f0
commit
a36dfbb2aa
|
@ -1,8 +1,8 @@
|
||||||
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
||||||
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
||||||
|
|
||||||
export function openDropdownMenu(id, placement, keyboard, scroll_key) {
|
export function openDropdownMenu(id, keyboard, scroll_key) {
|
||||||
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
|
return { type: DROPDOWN_MENU_OPEN, id, keyboard, scroll_key };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeDropdownMenu(id) {
|
export function closeDropdownMenu(id) {
|
||||||
|
|
|
@ -2,9 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import IconButton from './icon_button';
|
import IconButton from './icon_button';
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
import Motion from '../features/ui/util/optional_motion';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CircularProgress } from 'flavours/glitch/components/loading_indicator';
|
import { CircularProgress } from 'flavours/glitch/components/loading_indicator';
|
||||||
|
@ -24,9 +22,6 @@ class DropdownMenu extends React.PureComponent {
|
||||||
scrollable: PropTypes.bool,
|
scrollable: PropTypes.bool,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
placement: PropTypes.string,
|
|
||||||
arrowOffsetLeft: PropTypes.string,
|
|
||||||
arrowOffsetTop: PropTypes.string,
|
|
||||||
openedViaKeyboard: PropTypes.bool,
|
openedViaKeyboard: PropTypes.bool,
|
||||||
renderItem: PropTypes.func,
|
renderItem: PropTypes.func,
|
||||||
renderHeader: PropTypes.func,
|
renderHeader: PropTypes.func,
|
||||||
|
@ -35,11 +30,6 @@ class DropdownMenu extends React.PureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
style: {},
|
style: {},
|
||||||
placement: 'bottom',
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
mounted: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDocumentClick = e => {
|
handleDocumentClick = e => {
|
||||||
|
@ -56,8 +46,6 @@ class DropdownMenu extends React.PureComponent {
|
||||||
if (this.focusedItem && this.props.openedViaKeyboard) {
|
if (this.focusedItem && this.props.openedViaKeyboard) {
|
||||||
this.focusedItem.focus({ preventScroll: true });
|
this.focusedItem.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ mounted: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -139,40 +127,28 @@ class DropdownMenu extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop, scrollable, renderHeader, loading } = this.props;
|
const { items, scrollable, renderHeader, loading } = this.props;
|
||||||
const { mounted } = this.state;
|
|
||||||
|
|
||||||
let renderItem = this.props.renderItem || this.renderItem;
|
let renderItem = this.props.renderItem || this.renderItem;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
{loading && (
|
||||||
// It should not be transformed when mounting because the resulting
|
<CircularProgress size={30} strokeWidth={3.5} />
|
||||||
// size will be used to determine the coordinate of the menu by
|
)}
|
||||||
// react-overlays
|
|
||||||
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
|
||||||
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
|
||||||
|
|
||||||
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })}>
|
{!loading && renderHeader && (
|
||||||
{loading && (
|
<div className='dropdown-menu__container__header'>
|
||||||
<CircularProgress size={30} strokeWidth={3.5} />
|
{renderHeader(items)}
|
||||||
)}
|
|
||||||
|
|
||||||
{!loading && renderHeader && (
|
|
||||||
<div className='dropdown-menu__container__header'>
|
|
||||||
{renderHeader(items)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!loading && (
|
|
||||||
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
|
|
||||||
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Motion>
|
|
||||||
|
{!loading && (
|
||||||
|
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
|
||||||
|
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +173,6 @@ export default class Dropdown extends React.PureComponent {
|
||||||
isUserTouching: PropTypes.func,
|
isUserTouching: PropTypes.func,
|
||||||
onOpen: PropTypes.func.isRequired,
|
onOpen: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
dropdownPlacement: PropTypes.string,
|
|
||||||
openDropdownId: PropTypes.number,
|
openDropdownId: PropTypes.number,
|
||||||
openedViaKeyboard: PropTypes.bool,
|
openedViaKeyboard: PropTypes.bool,
|
||||||
renderItem: PropTypes.func,
|
renderItem: PropTypes.func,
|
||||||
|
@ -213,13 +188,11 @@ export default class Dropdown extends React.PureComponent {
|
||||||
id: id++,
|
id: id++,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = ({ target, type }) => {
|
handleClick = ({ type }) => {
|
||||||
if (this.state.id === this.props.openDropdownId) {
|
if (this.state.id === this.props.openDropdownId) {
|
||||||
this.handleClose();
|
this.handleClose();
|
||||||
} else {
|
} else {
|
||||||
const { top } = target.getBoundingClientRect();
|
this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
|
||||||
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
|
|
||||||
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +276,6 @@ export default class Dropdown extends React.PureComponent {
|
||||||
disabled,
|
disabled,
|
||||||
loading,
|
loading,
|
||||||
scrollable,
|
scrollable,
|
||||||
dropdownPlacement,
|
|
||||||
openDropdownId,
|
openDropdownId,
|
||||||
openedViaKeyboard,
|
openedViaKeyboard,
|
||||||
children,
|
children,
|
||||||
|
@ -314,7 +286,6 @@ export default class Dropdown extends React.PureComponent {
|
||||||
const open = this.state.id === openDropdownId;
|
const open = this.state.id === openDropdownId;
|
||||||
|
|
||||||
const button = children ? React.cloneElement(React.Children.only(children), {
|
const button = children ? React.cloneElement(React.Children.only(children), {
|
||||||
ref: this.setTargetRef,
|
|
||||||
onClick: this.handleClick,
|
onClick: this.handleClick,
|
||||||
onMouseDown: this.handleMouseDown,
|
onMouseDown: this.handleMouseDown,
|
||||||
onKeyDown: this.handleButtonKeyDown,
|
onKeyDown: this.handleButtonKeyDown,
|
||||||
|
@ -326,7 +297,6 @@ export default class Dropdown extends React.PureComponent {
|
||||||
active={open}
|
active={open}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
size={size}
|
size={size}
|
||||||
ref={this.setTargetRef}
|
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onMouseDown={this.handleMouseDown}
|
onMouseDown={this.handleMouseDown}
|
||||||
onKeyDown={this.handleButtonKeyDown}
|
onKeyDown={this.handleButtonKeyDown}
|
||||||
|
@ -336,19 +306,27 @@ export default class Dropdown extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{button}
|
<span ref={this.setTargetRef}>
|
||||||
|
{button}
|
||||||
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
|
</span>
|
||||||
<DropdownMenu
|
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
|
||||||
items={items}
|
{({ props, arrowProps, placement }) => (
|
||||||
loading={loading}
|
<div {...props}>
|
||||||
scrollable={scrollable}
|
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
||||||
onClose={this.handleClose}
|
<div className={`dropdown-menu__arrow ${placement}`} {...arrowProps} />
|
||||||
openedViaKeyboard={openedViaKeyboard}
|
<DropdownMenu
|
||||||
renderItem={renderItem}
|
items={items}
|
||||||
renderHeader={renderHeader}
|
loading={loading}
|
||||||
onItemClick={this.handleItemClick}
|
scrollable={scrollable}
|
||||||
/>
|
onClose={this.handleClose}
|
||||||
|
openedViaKeyboard={openedViaKeyboard}
|
||||||
|
renderItem={renderItem}
|
||||||
|
renderHeader={renderHeader}
|
||||||
|
onItemClick={this.handleItemClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { fetchHistory } from 'flavours/glitch/actions/history';
|
||||||
import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
|
import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
|
||||||
|
|
||||||
const mapStateToProps = (state, { statusId }) => ({
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
|
|
||||||
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
||||||
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
||||||
items: state.getIn(['history', statusId, 'items']),
|
items: state.getIn(['history', statusId, 'items']),
|
||||||
|
@ -13,9 +12,9 @@ const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { statusId }) => ({
|
const mapDispatchToProps = (dispatch, { statusId }) => ({
|
||||||
|
|
||||||
onOpen (id, onItemClick, dropdownPlacement, keyboard) {
|
onOpen (id, onItemClick, keyboard) {
|
||||||
dispatch(fetchHistory(statusId));
|
dispatch(fetchHistory(statusId));
|
||||||
dispatch(openDropdownMenu(id, dropdownPlacement, keyboard));
|
dispatch(openDropdownMenu(id, keyboard));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClose (id) {
|
onClose (id) {
|
||||||
|
|
|
@ -5,18 +5,17 @@ import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
|
||||||
import { isUserTouching } from '../is_mobile';
|
import { isUserTouching } from '../is_mobile';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
|
|
||||||
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
||||||
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
||||||
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
|
onOpen(id, onItemClick, keyboard) {
|
||||||
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
||||||
status,
|
status,
|
||||||
actions: items,
|
actions: items,
|
||||||
onClick: onItemClick,
|
onClick: onItemClick,
|
||||||
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
|
}) : openDropdownMenu(id, keyboard, scrollKey));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClose(id) {
|
onClose(id) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
// Components.
|
// Components.
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
|
@ -45,7 +45,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggles opening and closing the dropdown.
|
// Toggles opening and closing the dropdown.
|
||||||
handleToggle = ({ target, type }) => {
|
handleToggle = ({ type }) => {
|
||||||
const { onModalOpen } = this.props;
|
const { onModalOpen } = this.props;
|
||||||
const { open } = this.state;
|
const { open } = this.state;
|
||||||
|
|
||||||
|
@ -59,11 +59,9 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { top } = target.getBoundingClientRect();
|
|
||||||
if (this.state.open && this.activeElement) {
|
if (this.state.open && this.activeElement) {
|
||||||
this.activeElement.focus({ preventScroll: true });
|
this.activeElement.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
|
||||||
this.setState({ open: !this.state.open, openedViaKeyboard: type !== 'click' });
|
this.setState({ open: !this.state.open, openedViaKeyboard: type !== 'click' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,6 +156,18 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTargetRef = c => {
|
||||||
|
this.target = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
findTarget = () => {
|
||||||
|
return this.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOverlayEnter = (state) => {
|
||||||
|
this.setState({ placement: state.placement });
|
||||||
|
}
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
|
@ -179,6 +189,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
<div
|
<div
|
||||||
className={classNames('privacy-dropdown', placement, { active: open })}
|
className={classNames('privacy-dropdown', placement, { active: open })}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
|
ref={this.setTargetRef}
|
||||||
>
|
>
|
||||||
<div className={classNames('privacy-dropdown__value', { active })}>
|
<div className={classNames('privacy-dropdown__value', { active })}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -204,18 +215,26 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
containerPadding={20}
|
containerPadding={20}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
show={open}
|
show={open}
|
||||||
target={this}
|
flip
|
||||||
|
target={this.findTarget}
|
||||||
container={container}
|
container={container}
|
||||||
|
popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}
|
||||||
>
|
>
|
||||||
<DropdownMenu
|
{({ props, placement }) => (
|
||||||
items={items}
|
<div {...props} style={{ ...props.style, width: 350, maxWidth: '100vw' }}>
|
||||||
renderItemContents={renderItemContents}
|
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
||||||
onChange={onChange}
|
<DropdownMenu
|
||||||
onClose={this.handleClose}
|
items={items}
|
||||||
value={value}
|
renderItemContents={renderItemContents}
|
||||||
openedViaKeyboard={this.state.openedViaKeyboard}
|
onChange={onChange}
|
||||||
closeOnChange={closeOnChange}
|
onClose={this.handleClose}
|
||||||
/>
|
value={value}
|
||||||
|
openedViaKeyboard={this.state.openedViaKeyboard}
|
||||||
|
closeOnChange={closeOnChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Package imports.
|
// Package imports.
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
@ -10,15 +9,8 @@ import Icon from 'flavours/glitch/components/icon';
|
||||||
|
|
||||||
// Utils.
|
// Utils.
|
||||||
import { withPassive } from 'flavours/glitch/utils/dom_helpers';
|
import { withPassive } from 'flavours/glitch/utils/dom_helpers';
|
||||||
import Motion from '../../ui/util/optional_motion';
|
|
||||||
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
|
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
|
||||||
|
|
||||||
// The spring to use with our motion.
|
|
||||||
const springMotion = spring(1, {
|
|
||||||
damping: 35,
|
|
||||||
stiffness: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
// The component.
|
// The component.
|
||||||
export default class ComposerOptionsDropdownContent extends React.PureComponent {
|
export default class ComposerOptionsDropdownContent extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -44,7 +36,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
mounted: false,
|
|
||||||
value: this.props.openedViaKeyboard ? this.props.items[0].name : undefined,
|
value: this.props.openedViaKeyboard ? this.props.items[0].name : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,7 +47,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores our node in `this.node`.
|
// Stores our node in `this.node`.
|
||||||
handleRef = (node) => {
|
setRef = (node) => {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +60,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
} else {
|
} else {
|
||||||
this.node.firstChild.focus({ preventScroll: true });
|
this.node.firstChild.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
this.setState({ mounted: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// On unmounting, we remove our listeners.
|
// On unmounting, we remove our listeners.
|
||||||
|
@ -191,7 +181,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const { mounted } = this.state;
|
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -201,36 +190,9 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return (
|
return (
|
||||||
<Motion
|
<div style={{ ...style }} role='listbox' ref={this.setRef}>
|
||||||
defaultStyle={{
|
{!!items && items.map((item, i) => this.renderItem(item, i))}
|
||||||
opacity: 0,
|
</div>
|
||||||
scaleX: 0.85,
|
|
||||||
scaleY: 0.75,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
opacity: springMotion,
|
|
||||||
scaleX: springMotion,
|
|
||||||
scaleY: springMotion,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
|
||||||
// It should not be transformed when mounting because the resulting
|
|
||||||
// size will be used to determine the coordinate of the menu by
|
|
||||||
// react-overlays
|
|
||||||
<div
|
|
||||||
className='privacy-dropdown__dropdown'
|
|
||||||
ref={this.handleRef}
|
|
||||||
role='listbox'
|
|
||||||
style={{
|
|
||||||
...style,
|
|
||||||
opacity: opacity,
|
|
||||||
transform: mounted ? `scale(${scaleX}, ${scaleY})` : null,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!!items && items.map((item, i) => this.renderItem(item, i))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
|
@ -155,9 +155,6 @@ class EmojiPickerMenu extends React.PureComponent {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onPick: PropTypes.func.isRequired,
|
onPick: PropTypes.func.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
placement: PropTypes.string,
|
|
||||||
arrowOffsetLeft: PropTypes.string,
|
|
||||||
arrowOffsetTop: PropTypes.string,
|
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
skinTone: PropTypes.number.isRequired,
|
skinTone: PropTypes.number.isRequired,
|
||||||
onSkinTone: PropTypes.func.isRequired,
|
onSkinTone: PropTypes.func.isRequired,
|
||||||
|
@ -326,14 +323,13 @@ class EmojiPickerDropdown extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
active: false,
|
active: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
placement: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setRef = (c) => {
|
setRef = (c) => {
|
||||||
this.dropdown = c;
|
this.dropdown = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowDropdown = ({ target }) => {
|
onShowDropdown = () => {
|
||||||
this.setState({ active: true });
|
this.setState({ active: true });
|
||||||
|
|
||||||
if (!EmojiPicker) {
|
if (!EmojiPicker) {
|
||||||
|
@ -348,9 +344,6 @@ class EmojiPickerDropdown extends React.PureComponent {
|
||||||
this.setState({ loading: false, active: false });
|
this.setState({ loading: false, active: false });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top } = target.getBoundingClientRect();
|
|
||||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onHideDropdown = () => {
|
onHideDropdown = () => {
|
||||||
|
@ -384,7 +377,7 @@ class EmojiPickerDropdown extends React.PureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
|
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
|
||||||
const title = intl.formatMessage(messages.emoji);
|
const title = intl.formatMessage(messages.emoji);
|
||||||
const { active, loading, placement } = this.state;
|
const { active, loading } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
|
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
|
||||||
|
@ -396,16 +389,22 @@ class EmojiPickerDropdown extends React.PureComponent {
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Overlay show={active} placement={placement} target={this.findTarget}>
|
<Overlay show={active} placement={'bottom'} target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
|
||||||
<EmojiPickerMenu
|
{({ props, placement })=> (
|
||||||
custom_emojis={this.props.custom_emojis}
|
<div {...props} style={{ ...props.style, width: 299 }}>
|
||||||
loading={loading}
|
<div className={`dropdown-animation ${placement}`}>
|
||||||
onClose={this.onHideDropdown}
|
<EmojiPickerMenu
|
||||||
onPick={onPickEmoji}
|
custom_emojis={this.props.custom_emojis}
|
||||||
onSkinTone={onSkinTone}
|
loading={loading}
|
||||||
skinTone={skinTone}
|
onClose={this.onHideDropdown}
|
||||||
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
onPick={onPickEmoji}
|
||||||
/>
|
onSkinTone={onSkinTone}
|
||||||
|
skinTone={skinTone}
|
||||||
|
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,9 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
import TextIconButton from './text_icon_button';
|
import TextIconButton from './text_icon_button';
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
import Motion from 'flavours/glitch/features/ui/util/optional_motion';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { languages as preloadedLanguages } from 'flavours/glitch/initial_state';
|
import { languages as preloadedLanguages } from 'flavours/glitch/initial_state';
|
||||||
|
@ -22,10 +20,8 @@ const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||||
class LanguageDropdownMenu extends React.PureComponent {
|
class LanguageDropdownMenu extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
style: PropTypes.object,
|
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
|
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
placement: PropTypes.string.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
||||||
|
@ -37,7 +33,6 @@ class LanguageDropdownMenu extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
mounted: false,
|
|
||||||
searchValue: '',
|
searchValue: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +45,6 @@ class LanguageDropdownMenu extends React.PureComponent {
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
document.addEventListener('click', this.handleDocumentClick, false);
|
document.addEventListener('click', this.handleDocumentClick, false);
|
||||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||||
this.setState({ mounted: true });
|
|
||||||
|
|
||||||
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
|
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
|
||||||
// to wait for a frame before focusing
|
// to wait for a frame before focusing
|
||||||
|
@ -222,29 +216,22 @@ class LanguageDropdownMenu extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { style, placement, intl } = this.props;
|
const { intl } = this.props;
|
||||||
const { mounted, searchValue } = this.state;
|
const { searchValue } = this.state;
|
||||||
const isSearching = searchValue !== '';
|
const isSearching = searchValue !== '';
|
||||||
const results = this.search();
|
const results = this.search();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
<div ref={this.setRef}>
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
<div className='emoji-mart-search'>
|
||||||
// It should not be transformed when mounting because the resulting
|
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
|
||||||
// size will be used to determine the coordinate of the menu by
|
<button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
|
||||||
// react-overlays
|
</div>
|
||||||
<div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
|
||||||
<div className='emoji-mart-search'>
|
|
||||||
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
|
|
||||||
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
|
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
|
||||||
{results.map(this.renderItem)}
|
{results.map(this.renderItem)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,14 +253,11 @@ class LanguageDropdown extends React.PureComponent {
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
};
|
};
|
||||||
|
|
||||||
handleToggle = ({ target }) => {
|
handleToggle = () => {
|
||||||
const { top } = target.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (this.state.open && this.activeElement) {
|
if (this.state.open && this.activeElement) {
|
||||||
this.activeElement.focus({ preventScroll: true });
|
this.activeElement.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
|
||||||
this.setState({ open: !this.state.open });
|
this.setState({ open: !this.state.open });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,13 +277,25 @@ class LanguageDropdown extends React.PureComponent {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTargetRef = c => {
|
||||||
|
this.target = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
findTarget = () => {
|
||||||
|
return this.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOverlayEnter = (state) => {
|
||||||
|
this.setState({ placement: state.placement });
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value, intl, frequentlyUsedLanguages } = this.props;
|
const { value, intl, frequentlyUsedLanguages } = this.props;
|
||||||
const { open, placement } = this.state;
|
const { open, placement } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('privacy-dropdown', { active: open })}>
|
<div className={classNames('privacy-dropdown', placement, { active: open })}>
|
||||||
<div className='privacy-dropdown__value'>
|
<div className='privacy-dropdown__value' ref={this.setTargetRef} >
|
||||||
<TextIconButton
|
<TextIconButton
|
||||||
className='privacy-dropdown__value-icon'
|
className='privacy-dropdown__value-icon'
|
||||||
label={value && value.toUpperCase()}
|
label={value && value.toUpperCase()}
|
||||||
|
@ -309,15 +305,20 @@ class LanguageDropdown extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Overlay show={open} placement={placement} target={this}>
|
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
|
||||||
<LanguageDropdownMenu
|
{({ props, placement }) => (
|
||||||
value={value}
|
<div {...props} style={{ ...props.style, width: 280 }}>
|
||||||
frequentlyUsedLanguages={frequentlyUsedLanguages}
|
<div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
|
||||||
onClose={this.handleClose}
|
<LanguageDropdownMenu
|
||||||
onChange={this.handleChange}
|
value={value}
|
||||||
placement={placement}
|
frequentlyUsedLanguages={frequentlyUsedLanguages}
|
||||||
intl={intl}
|
onClose={this.handleClose}
|
||||||
/>
|
onChange={this.handleChange}
|
||||||
|
intl={intl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,13 +3,12 @@ import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import {
|
import {
|
||||||
injectIntl,
|
injectIntl,
|
||||||
FormattedMessage,
|
FormattedMessage,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
// Components.
|
// Components.
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
|
@ -17,7 +16,6 @@ import Icon from 'flavours/glitch/components/icon';
|
||||||
// Utils.
|
// Utils.
|
||||||
import { focusRoot } from 'flavours/glitch/utils/dom_helpers';
|
import { focusRoot } from 'flavours/glitch/utils/dom_helpers';
|
||||||
import { searchEnabled } from 'flavours/glitch/initial_state';
|
import { searchEnabled } from 'flavours/glitch/initial_state';
|
||||||
import Motion from '../../ui/util/optional_motion';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||||
|
@ -26,31 +24,20 @@ const messages = defineMessages({
|
||||||
|
|
||||||
class SearchPopout extends React.PureComponent {
|
class SearchPopout extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
style: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { style } = this.props;
|
|
||||||
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
|
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
|
||||||
return (
|
return (
|
||||||
<div style={{ ...style, position: 'absolute', width: 285, zIndex: 2 }}>
|
<div className='search-popout'>
|
||||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
|
||||||
<div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
|
|
||||||
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
|
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
|
||||||
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
|
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
|
||||||
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
|
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
|
||||||
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
|
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{extraInformation}
|
{extraInformation}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -136,6 +123,10 @@ class Search extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findTarget = () => {
|
||||||
|
return this.searchForm;
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, value, submitted } = this.props;
|
const { intl, value, submitted } = this.props;
|
||||||
const { expanded } = this.state;
|
const { expanded } = this.state;
|
||||||
|
@ -161,8 +152,14 @@ class Search extends React.PureComponent {
|
||||||
<Icon id='search' className={hasValue ? '' : 'active'} />
|
<Icon id='search' className={hasValue ? '' : 'active'} />
|
||||||
<Icon id='times-circle' className={hasValue ? 'active' : ''} />
|
<Icon id='times-circle' className={hasValue ? 'active' : ''} />
|
||||||
</div>
|
</div>
|
||||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this} container={this}>
|
<Overlay show={expanded && !hasValue} placement='bottom' target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
|
||||||
<SearchPopout />
|
{({ props, placement }) => (
|
||||||
|
<div {...props} style={{ ...props.style, width: 285, zIndex: 2 }}>
|
||||||
|
<div className={`dropdown-animation ${placement}`}>
|
||||||
|
<SearchPopout />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,12 +4,12 @@ import {
|
||||||
DROPDOWN_MENU_CLOSE,
|
DROPDOWN_MENU_CLOSE,
|
||||||
} from '../actions/dropdown_menu';
|
} from '../actions/dropdown_menu';
|
||||||
|
|
||||||
const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false, scroll_key: null });
|
const initialState = Immutable.Map({ openId: null, keyboard: false, scroll_key: null });
|
||||||
|
|
||||||
export default function dropdownMenu(state = initialState, action) {
|
export default function dropdownMenu(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case DROPDOWN_MENU_OPEN:
|
case DROPDOWN_MENU_OPEN:
|
||||||
return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard, scroll_key: action.scroll_key });
|
return state.merge({ openId: action.id, keyboard: action.keyboard, scroll_key: action.scroll_key });
|
||||||
case DROPDOWN_MENU_CLOSE:
|
case DROPDOWN_MENU_CLOSE:
|
||||||
return state.get('openId') === action.id ? state.set('openId', null).set('scroll_key', null) : state;
|
return state.get('openId') === action.id ? state.set('openId', null).set('scroll_key', null) : state;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -586,7 +586,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.privacy-dropdown__dropdown {
|
.privacy-dropdown__dropdown {
|
||||||
position: absolute;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||||
background: $simple-background-color;
|
background: $simple-background-color;
|
||||||
|
@ -653,7 +652,6 @@
|
||||||
|
|
||||||
.language-dropdown {
|
.language-dropdown {
|
||||||
&__dropdown {
|
&__dropdown {
|
||||||
position: absolute;
|
|
||||||
background: $simple-background-color;
|
background: $simple-background-color;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
|
@ -346,9 +346,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
body > [data-popper-placement] {
|
||||||
position: absolute;
|
z-index: 3;
|
||||||
transform-origin: 50% 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invisible {
|
.invisible {
|
||||||
|
@ -532,6 +531,42 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-animation {
|
||||||
|
animation: dropdown 300ms cubic-bezier(0.1, 0.7, 0.1, 1);
|
||||||
|
|
||||||
|
@keyframes dropdown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX(0.85) scaleY(0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scaleX(1) scaleY(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top {
|
||||||
|
transform-origin: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reduce-motion & {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -600,36 +635,42 @@
|
||||||
|
|
||||||
.dropdown-menu__arrow {
|
.dropdown-menu__arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border: 0 solid transparent;
|
|
||||||
|
|
||||||
&.left {
|
&::before {
|
||||||
right: -5px;
|
content: '';
|
||||||
margin-top: -5px;
|
display: block;
|
||||||
border-width: 5px 0 5px 5px;
|
width: 14px;
|
||||||
border-left-color: $ui-secondary-color;
|
height: 5px;
|
||||||
|
background-color: $ui-secondary-color;
|
||||||
|
mask-image: url("data:image/svg+xml;utf8,<svg width='14' height='5' xmlns='http://www.w3.org/2000/svg'><path d='M7 0L0 5h14L7 0z' fill='white'/></svg>");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.top {
|
&.top {
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
margin-left: -7px;
|
|
||||||
border-width: 5px 7px 0;
|
&::before {
|
||||||
border-top-color: $ui-secondary-color;
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
left: -9px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bottom {
|
&.bottom {
|
||||||
top: -5px;
|
top: -5px;
|
||||||
margin-left: -7px;
|
|
||||||
border-width: 0 7px 5px;
|
|
||||||
border-bottom-color: $ui-secondary-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.right {
|
&.left {
|
||||||
left: -5px;
|
right: -9px;
|
||||||
margin-top: -5px;
|
|
||||||
border-width: 5px 5px 5px 0;
|
&::before {
|
||||||
border-right-color: $ui-secondary-color;
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
.modal-root__modal {
|
.modal-root__modal {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 9999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__zoom-button {
|
.media-modal__zoom-button {
|
||||||
|
|
|
@ -285,22 +285,8 @@ html {
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
background: $white;
|
background: $white;
|
||||||
|
|
||||||
&__arrow {
|
&__arrow::before {
|
||||||
&.left {
|
background-color: $white;
|
||||||
border-left-color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.top {
|
|
||||||
border-top-color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bottom {
|
|
||||||
border-bottom-color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.right {
|
|
||||||
border-right-color: $white;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
|
|
Loading…
Reference in New Issue