Refactor composer dropdowns
This commit is contained in:
parent
0e77c45624
commit
3564a15553
3 changed files with 96 additions and 118 deletions
|
@ -1,17 +1,14 @@
|
||||||
import { useCallback, useState, useRef } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import Overlay from 'react-overlays/Overlay';
|
|
||||||
|
|
||||||
import CodeIcon from '@/material-icons/400-24px/code.svg?react';
|
import CodeIcon from '@/material-icons/400-24px/code.svg?react';
|
||||||
import DescriptionIcon from '@/material-icons/400-24px/description.svg?react';
|
import DescriptionIcon from '@/material-icons/400-24px/description.svg?react';
|
||||||
import MarkdownIcon from '@/material-icons/400-24px/markdown.svg?react';
|
import MarkdownIcon from '@/material-icons/400-24px/markdown.svg?react';
|
||||||
import { changeComposeContentType } from 'flavours/glitch/actions/compose';
|
import { changeComposeContentType } from 'flavours/glitch/actions/compose';
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import DropdownMenu from './dropdown_menu';
|
import { DropdownIconButton } from './dropdown_icon_button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
change_content_type: { id: 'compose.content-type.change', defaultMessage: 'Change advanced formatting options' },
|
change_content_type: { id: 'compose.content-type.change', defaultMessage: 'Change advanced formatting options' },
|
||||||
|
@ -30,38 +27,10 @@ export const ContentTypeButton = () => {
|
||||||
const contentType = useAppSelector((state) => state.getIn(['compose', 'content_type']));
|
const contentType = useAppSelector((state) => state.getIn(['compose', 'content_type']));
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const containerRef = useRef(null);
|
|
||||||
|
|
||||||
const [activeElement, setActiveElement] = useState(null);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [placement, setPlacement] = useState('bottom');
|
|
||||||
|
|
||||||
const handleToggle = useCallback(() => {
|
|
||||||
if (open && activeElement) {
|
|
||||||
activeElement.focus({ preventScroll: true });
|
|
||||||
setActiveElement(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(!open);
|
|
||||||
}, [open, setOpen, activeElement, setActiveElement]);
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
if (open && activeElement) {
|
|
||||||
activeElement.focus({ preventScroll: true });
|
|
||||||
setActiveElement(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(false);
|
|
||||||
}, [open, setOpen, activeElement, setActiveElement]);
|
|
||||||
|
|
||||||
const handleChange = useCallback((value) => {
|
const handleChange = useCallback((value) => {
|
||||||
dispatch(changeComposeContentType(value));
|
dispatch(changeComposeContentType(value));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleOverlayEnter = useCallback((state) => {
|
|
||||||
setPlacement(state.placement);
|
|
||||||
}, [setPlacement]);
|
|
||||||
|
|
||||||
if (!showButton) {
|
if (!showButton) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -85,31 +54,13 @@ export const ContentTypeButton = () => {
|
||||||
}[contentType];
|
}[contentType];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<DropdownIconButton
|
||||||
<IconButton
|
|
||||||
icon={icon}
|
icon={icon}
|
||||||
onClick={handleToggle}
|
|
||||||
iconComponent={iconComponent}
|
iconComponent={iconComponent}
|
||||||
title={intl.formatMessage(messages.change_content_type)}
|
|
||||||
active={open}
|
|
||||||
size={18}
|
|
||||||
inverted
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Overlay show={open} offset={[5, 5]} placement={placement} flip target={containerRef} popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}>
|
|
||||||
{({ props, placement }) => (
|
|
||||||
<div {...props}>
|
|
||||||
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
|
||||||
<DropdownMenu
|
|
||||||
items={options}
|
|
||||||
value={contentType}
|
|
||||||
onClose={handleClose}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
title={intl.formatMessage(messages.change_content_type)}
|
||||||
|
value={contentType}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Overlay>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useCallback, useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
|
|
||||||
|
import DropdownMenu from './dropdown_menu';
|
||||||
|
|
||||||
|
export const DropdownIconButton = ({ value, icon, onChange, iconComponent, title, options }) => {
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
|
const [activeElement, setActiveElement] = useState(null);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [placement, setPlacement] = useState('bottom');
|
||||||
|
|
||||||
|
const handleToggle = useCallback(() => {
|
||||||
|
if (open && activeElement) {
|
||||||
|
activeElement.focus({ preventScroll: true });
|
||||||
|
setActiveElement(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen(!open);
|
||||||
|
}, [open, setOpen, activeElement, setActiveElement]);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
if (open && activeElement) {
|
||||||
|
activeElement.focus({ preventScroll: true });
|
||||||
|
setActiveElement(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
}, [open, setOpen, activeElement, setActiveElement]);
|
||||||
|
|
||||||
|
const handleOverlayEnter = useCallback((state) => {
|
||||||
|
setPlacement(state.placement);
|
||||||
|
}, [setPlacement]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef}>
|
||||||
|
<IconButton
|
||||||
|
icon={icon}
|
||||||
|
onClick={handleToggle}
|
||||||
|
iconComponent={iconComponent}
|
||||||
|
title={title}
|
||||||
|
active={open}
|
||||||
|
size={18}
|
||||||
|
inverted
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Overlay show={open} offset={[5, 5]} placement={placement} flip target={containerRef} popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}>
|
||||||
|
{({ props, placement }) => (
|
||||||
|
<div {...props}>
|
||||||
|
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
||||||
|
<DropdownMenu
|
||||||
|
items={options}
|
||||||
|
value={value}
|
||||||
|
onClose={handleClose}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Overlay>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DropdownIconButton.propTypes = {
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
iconComponent: PropTypes.func.isRequired,
|
||||||
|
options: PropTypes.array.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
};
|
|
@ -1,16 +1,13 @@
|
||||||
import { useCallback, useState, useRef } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import Overlay from 'react-overlays/Overlay';
|
|
||||||
|
|
||||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||||
import ShareOffIcon from '@/material-icons/400-24px/share_off.svg?react';
|
import ShareOffIcon from '@/material-icons/400-24px/share_off.svg?react';
|
||||||
import { changeComposeAdvancedOption } from 'flavours/glitch/actions/compose';
|
import { changeComposeAdvancedOption } from 'flavours/glitch/actions/compose';
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import DropdownMenu from './dropdown_menu';
|
import { DropdownIconButton } from './dropdown_icon_button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
change_federation_settings: { id: 'compose.change_federation', defaultMessage: 'Change federation settings' },
|
change_federation_settings: { id: 'compose.change_federation', defaultMessage: 'Change federation settings' },
|
||||||
|
@ -26,69 +23,23 @@ export const FederationButton = () => {
|
||||||
const do_not_federate = useAppSelector((state) => state.getIn(['compose', 'advanced_options', 'do_not_federate']));
|
const do_not_federate = useAppSelector((state) => state.getIn(['compose', 'advanced_options', 'do_not_federate']));
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const containerRef = useRef(null);
|
|
||||||
|
|
||||||
const [activeElement, setActiveElement] = useState(null);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [placement, setPlacement] = useState('bottom');
|
|
||||||
|
|
||||||
const handleToggle = useCallback(() => {
|
|
||||||
if (open && activeElement) {
|
|
||||||
activeElement.focus({ preventScroll: true });
|
|
||||||
setActiveElement(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(!open);
|
|
||||||
}, [open, setOpen, activeElement, setActiveElement]);
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
if (open && activeElement) {
|
|
||||||
activeElement.focus({ preventScroll: true });
|
|
||||||
setActiveElement(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(false);
|
|
||||||
}, [open, setOpen, activeElement, setActiveElement]);
|
|
||||||
|
|
||||||
const handleChange = useCallback((value) => {
|
const handleChange = useCallback((value) => {
|
||||||
dispatch(changeComposeAdvancedOption('do_not_federate', value === 'local-only'));
|
dispatch(changeComposeAdvancedOption('do_not_federate', value === 'local-only'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleOverlayEnter = useCallback((state) => {
|
|
||||||
setPlacement(state.placement);
|
|
||||||
}, [setPlacement]);
|
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{ icon: 'link', iconComponent: ShareIcon, value: 'federated', text: intl.formatMessage(messages.federated_label), meta: intl.formatMessage(messages.federated_meta) },
|
{ icon: 'link', iconComponent: ShareIcon, value: 'federated', text: intl.formatMessage(messages.federated_label), meta: intl.formatMessage(messages.federated_meta) },
|
||||||
{ icon: 'link-slash', iconComponent: ShareOffIcon, value: 'local-only', text: intl.formatMessage(messages.local_only_label), meta: intl.formatMessage(messages.local_only_meta) },
|
{ icon: 'link-slash', iconComponent: ShareOffIcon, value: 'local-only', text: intl.formatMessage(messages.local_only_label), meta: intl.formatMessage(messages.local_only_meta) },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<DropdownIconButton
|
||||||
<IconButton
|
|
||||||
icon={do_not_federate ? 'link-slash' : 'link'}
|
icon={do_not_federate ? 'link-slash' : 'link'}
|
||||||
onClick={handleToggle}
|
|
||||||
iconComponent={do_not_federate ? ShareOffIcon : ShareIcon}
|
iconComponent={do_not_federate ? ShareOffIcon : ShareIcon}
|
||||||
title={intl.formatMessage(messages.change_federation_settings)}
|
|
||||||
active={open}
|
|
||||||
size={18}
|
|
||||||
inverted
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Overlay show={open} offset={[5, 5]} placement={placement} flip target={containerRef} popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}>
|
|
||||||
{({ props, placement }) => (
|
|
||||||
<div {...props}>
|
|
||||||
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
|
||||||
<DropdownMenu
|
|
||||||
items={options}
|
|
||||||
value={do_not_federate ? 'local-only' : 'federated'}
|
|
||||||
onClose={handleClose}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
title={intl.formatMessage(messages.change_federation_settings)}
|
||||||
|
value={do_not_federate ? 'local-only' : 'federated'}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Overlay>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue