mirror of
https://github.com/mastodon/mastodon.git
synced 2025-05-07 12:16:14 +00:00
Refactor <ActionsModal>
to TypeScript (#34559)
This commit is contained in:
parent
17e4345eb2
commit
926c67c648
|
@ -26,11 +26,12 @@ import {
|
||||||
import { openModal, closeModal } from 'mastodon/actions/modal';
|
import { openModal, closeModal } from 'mastodon/actions/modal';
|
||||||
import { CircularProgress } from 'mastodon/components/circular_progress';
|
import { CircularProgress } from 'mastodon/components/circular_progress';
|
||||||
import { isUserTouching } from 'mastodon/is_mobile';
|
import { isUserTouching } from 'mastodon/is_mobile';
|
||||||
import type {
|
import {
|
||||||
MenuItem,
|
isMenuItem,
|
||||||
ActionMenuItem,
|
isActionItem,
|
||||||
ExternalLinkMenuItem,
|
isExternalLinkItem,
|
||||||
} from 'mastodon/models/dropdown_menu';
|
} from 'mastodon/models/dropdown_menu';
|
||||||
|
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
import type { IconProp } from './icon';
|
import type { IconProp } from './icon';
|
||||||
|
@ -38,30 +39,6 @@ import { IconButton } from './icon_button';
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
|
||||||
const isMenuItem = (item: unknown): item is MenuItem => {
|
|
||||||
if (item === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof item === 'object' && 'text' in item;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isActionItem = (item: unknown): item is ActionMenuItem => {
|
|
||||||
if (!item || !isMenuItem(item)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'action' in item;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isExternalLinkItem = (item: unknown): item is ExternalLinkMenuItem => {
|
|
||||||
if (!item || !isMenuItem(item)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'href' in item;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RenderItemFn<Item = MenuItem> = (
|
type RenderItemFn<Item = MenuItem> = (
|
||||||
item: Item,
|
item: Item,
|
||||||
index: number,
|
index: number,
|
||||||
|
@ -354,6 +331,9 @@ export const Dropdown = <Item = MenuItem,>({
|
||||||
const open = currentId === openDropdownId;
|
const open = currentId === openDropdownId;
|
||||||
const activeElement = useRef<HTMLElement | null>(null);
|
const activeElement = useRef<HTMLElement | null>(null);
|
||||||
const targetRef = useRef<HTMLButtonElement | null>(null);
|
const targetRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
const prefetchAccountId = status
|
||||||
|
? status.getIn(['account', 'id'])
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
if (activeElement.current) {
|
if (activeElement.current) {
|
||||||
|
@ -402,8 +382,8 @@ export const Dropdown = <Item = MenuItem,>({
|
||||||
} else {
|
} else {
|
||||||
onOpen?.();
|
onOpen?.();
|
||||||
|
|
||||||
if (status) {
|
if (prefetchAccountId) {
|
||||||
dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
|
dispatch(fetchRelationships([prefetchAccountId]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserTouching()) {
|
if (isUserTouching()) {
|
||||||
|
@ -411,7 +391,6 @@ export const Dropdown = <Item = MenuItem,>({
|
||||||
openModal({
|
openModal({
|
||||||
modalType: 'ACTIONS',
|
modalType: 'ACTIONS',
|
||||||
modalProps: {
|
modalProps: {
|
||||||
status,
|
|
||||||
actions: items,
|
actions: items,
|
||||||
onClick: handleItemClick,
|
onClick: handleItemClick,
|
||||||
},
|
},
|
||||||
|
@ -431,11 +410,11 @@ export const Dropdown = <Item = MenuItem,>({
|
||||||
[
|
[
|
||||||
dispatch,
|
dispatch,
|
||||||
currentId,
|
currentId,
|
||||||
|
prefetchAccountId,
|
||||||
scrollKey,
|
scrollKey,
|
||||||
onOpen,
|
onOpen,
|
||||||
handleItemClick,
|
handleItemClick,
|
||||||
open,
|
open,
|
||||||
status,
|
|
||||||
items,
|
items,
|
||||||
handleClose,
|
handleClose,
|
||||||
],
|
],
|
||||||
|
|
|
@ -30,9 +30,6 @@ const messages = defineMessages({
|
||||||
class PrivacyDropdown extends PureComponent {
|
class PrivacyDropdown extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isUserTouching: PropTypes.func,
|
|
||||||
onModalOpen: PropTypes.func,
|
|
||||||
onModalClose: PropTypes.func,
|
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
noDirect: PropTypes.bool,
|
noDirect: PropTypes.bool,
|
||||||
|
|
|
@ -15,16 +15,6 @@ const mapDispatchToProps = dispatch => ({
|
||||||
dispatch(changeComposeVisibility(value));
|
dispatch(changeComposeVisibility(value));
|
||||||
},
|
},
|
||||||
|
|
||||||
isUserTouching,
|
|
||||||
onModalOpen: props => dispatch(openModal({
|
|
||||||
modalType: 'ACTIONS',
|
|
||||||
modalProps: props,
|
|
||||||
})),
|
|
||||||
onModalClose: () => dispatch(closeModal({
|
|
||||||
modalType: undefined,
|
|
||||||
ignoreFocus: false,
|
|
||||||
})),
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
import { IconButton } from '../../../components/icon_button';
|
|
||||||
|
|
||||||
export default class ActionsModal extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
status: ImmutablePropTypes.map,
|
|
||||||
actions: PropTypes.array,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
renderAction = (action, i) => {
|
|
||||||
if (action === null) {
|
|
||||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { icon = null, iconComponent = null, text, meta = null, active = false, href = '#' } = action;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={`${text}-${i}`}>
|
|
||||||
<a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
|
|
||||||
{icon && <IconButton title={text} icon={icon} iconComponent={iconComponent} role='presentation' tabIndex={-1} inverted />}
|
|
||||||
<div>
|
|
||||||
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
|
|
||||||
<div>{meta}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal actions-modal'>
|
|
||||||
<ul className={classNames({ 'with-status': !!status })}>
|
|
||||||
{this.props.actions.map(this.renderAction)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||||
|
import {
|
||||||
|
isActionItem,
|
||||||
|
isExternalLinkItem,
|
||||||
|
} from 'mastodon/models/dropdown_menu';
|
||||||
|
|
||||||
|
export const ActionsModal: React.FC<{
|
||||||
|
actions: MenuItem[];
|
||||||
|
onClick: React.MouseEventHandler;
|
||||||
|
}> = ({ actions, onClick }) => (
|
||||||
|
<div className='modal-root__modal actions-modal'>
|
||||||
|
<ul>
|
||||||
|
{actions.map((option, i: number) => {
|
||||||
|
if (option === null) {
|
||||||
|
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text, dangerous } = option;
|
||||||
|
|
||||||
|
let element: React.ReactElement;
|
||||||
|
|
||||||
|
if (isActionItem(option)) {
|
||||||
|
element = (
|
||||||
|
<button onClick={onClick} data-index={i}>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
} else if (isExternalLinkItem(option)) {
|
||||||
|
element = (
|
||||||
|
<a
|
||||||
|
href={option.href}
|
||||||
|
target={option.target ?? '_target'}
|
||||||
|
data-method={option.method}
|
||||||
|
rel='noopener'
|
||||||
|
onClick={onClick}
|
||||||
|
data-index={i}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
element = (
|
||||||
|
<Link to={option.to} onClick={onClick} data-index={i}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classNames({
|
||||||
|
'dropdown-menu__item--dangerous': dangerous,
|
||||||
|
})}
|
||||||
|
key={`${text}-${i}`}
|
||||||
|
>
|
||||||
|
{element}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
|
@ -24,7 +24,7 @@ import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
||||||
|
|
||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
|
|
||||||
import ActionsModal from './actions_modal';
|
import { ActionsModal } from './actions_modal';
|
||||||
import AudioModal from './audio_modal';
|
import AudioModal from './audio_modal';
|
||||||
import { BoostModal } from './boost_modal';
|
import { BoostModal } from './boost_modal';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -22,3 +22,29 @@ export type MenuItem =
|
||||||
| LinkMenuItem
|
| LinkMenuItem
|
||||||
| ExternalLinkMenuItem
|
| ExternalLinkMenuItem
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
|
export const isMenuItem = (item: unknown): item is MenuItem => {
|
||||||
|
if (item === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof item === 'object' && 'text' in item;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isActionItem = (item: unknown): item is ActionMenuItem => {
|
||||||
|
if (!item || !isMenuItem(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'action' in item;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isExternalLinkItem = (
|
||||||
|
item: unknown,
|
||||||
|
): item is ExternalLinkMenuItem => {
|
||||||
|
if (!item || !isMenuItem(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'href' in item;
|
||||||
|
};
|
||||||
|
|
|
@ -6484,55 +6484,38 @@ a.status-card {
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-modal {
|
.actions-modal {
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
background: var(--dropdown-background-color);
|
||||||
|
backdrop-filter: var(--background-filter);
|
||||||
|
border-color: var(--dropdown-border-color);
|
||||||
|
box-shadow: var(--dropdown-shadow);
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
|
||||||
.actions-modal__item-label {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
max-height: 80vh;
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
&.with-status {
|
a,
|
||||||
max-height: calc(80vh - 75px);
|
button {
|
||||||
}
|
color: inherit;
|
||||||
|
display: flex;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 21px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
li:empty {
|
&:hover,
|
||||||
margin: 0;
|
&:active,
|
||||||
}
|
&:focus {
|
||||||
|
background: var(--dropdown-border-color);
|
||||||
li:not(:empty) {
|
|
||||||
a {
|
|
||||||
color: $primary-text-color;
|
|
||||||
display: flex;
|
|
||||||
padding: 12px 16px;
|
|
||||||
font-size: 15px;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&,
|
|
||||||
button {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active,
|
|
||||||
&:hover,
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
&,
|
|
||||||
button {
|
|
||||||
background: $ui-highlight-color;
|
|
||||||
color: $primary-text-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button:first-child {
|
|
||||||
margin-inline-end: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user