diff --git a/app/javascript/mastodon/components/dropdown_menu.tsx b/app/javascript/mastodon/components/dropdown_menu.tsx index 886d517fa9..c2fb93940c 100644 --- a/app/javascript/mastodon/components/dropdown_menu.tsx +++ b/app/javascript/mastodon/components/dropdown_menu.tsx @@ -26,11 +26,12 @@ import { import { openModal, closeModal } from 'mastodon/actions/modal'; import { CircularProgress } from 'mastodon/components/circular_progress'; import { isUserTouching } from 'mastodon/is_mobile'; -import type { - MenuItem, - ActionMenuItem, - ExternalLinkMenuItem, +import { + isMenuItem, + isActionItem, + isExternalLinkItem, } from 'mastodon/models/dropdown_menu'; +import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import type { IconProp } from './icon'; @@ -38,30 +39,6 @@ import { IconButton } from './icon_button'; 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: Item, index: number, @@ -354,6 +331,9 @@ export const Dropdown = ({ const open = currentId === openDropdownId; const activeElement = useRef(null); const targetRef = useRef(null); + const prefetchAccountId = status + ? status.getIn(['account', 'id']) + : undefined; const handleClose = useCallback(() => { if (activeElement.current) { @@ -402,8 +382,8 @@ export const Dropdown = ({ } else { onOpen?.(); - if (status) { - dispatch(fetchRelationships([status.getIn(['account', 'id'])])); + if (prefetchAccountId) { + dispatch(fetchRelationships([prefetchAccountId])); } if (isUserTouching()) { @@ -411,7 +391,6 @@ export const Dropdown = ({ openModal({ modalType: 'ACTIONS', modalProps: { - status, actions: items, onClick: handleItemClick, }, @@ -431,11 +410,11 @@ export const Dropdown = ({ [ dispatch, currentId, + prefetchAccountId, scrollKey, onOpen, handleItemClick, open, - status, items, handleClose, ], diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx index f474aecf28..15df5ab729 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx @@ -30,9 +30,6 @@ const messages = defineMessages({ class PrivacyDropdown extends PureComponent { static propTypes = { - isUserTouching: PropTypes.func, - onModalOpen: PropTypes.func, - onModalClose: PropTypes.func, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, noDirect: PropTypes.bool, diff --git a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js index 6d26abf4f6..6d3eef13aa 100644 --- a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js +++ b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js @@ -15,16 +15,6 @@ const mapDispatchToProps = dispatch => ({ 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); diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.jsx b/app/javascript/mastodon/features/ui/components/actions_modal.jsx deleted file mode 100644 index 851f828b4e..0000000000 --- a/app/javascript/mastodon/features/ui/components/actions_modal.jsx +++ /dev/null @@ -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
  • ; - } - - const { icon = null, iconComponent = null, text, meta = null, active = false, href = '#' } = action; - - return ( -
  • - - {icon && } -
    -
    {text}
    -
    {meta}
    -
    -
    -
  • - ); - }; - - render () { - return ( -
    -
      - {this.props.actions.map(this.renderAction)} -
    -
    - ); - } - -} diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.tsx b/app/javascript/mastodon/features/ui/components/actions_modal.tsx new file mode 100644 index 0000000000..da42b86392 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/actions_modal.tsx @@ -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 }) => ( +
    +
      + {actions.map((option, i: number) => { + if (option === null) { + return
    • ; + } + + const { text, dangerous } = option; + + let element: React.ReactElement; + + if (isActionItem(option)) { + element = ( + + ); + } else if (isExternalLinkItem(option)) { + element = ( + + {text} + + ); + } else { + element = ( + + {text} + + ); + } + + return ( +
    • + {element} +
    • + ); + })} +
    +
    +); diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 74fe59f322..ff13375177 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -24,7 +24,7 @@ import { getScrollbarWidth } from 'mastodon/utils/scrollbar'; import BundleContainer from '../containers/bundle_container'; -import ActionsModal from './actions_modal'; +import { ActionsModal } from './actions_modal'; import AudioModal from './audio_modal'; import { BoostModal } from './boost_modal'; import { diff --git a/app/javascript/mastodon/models/dropdown_menu.ts b/app/javascript/mastodon/models/dropdown_menu.ts index ceea9ad4dd..c02f205023 100644 --- a/app/javascript/mastodon/models/dropdown_menu.ts +++ b/app/javascript/mastodon/models/dropdown_menu.ts @@ -22,3 +22,29 @@ export type MenuItem = | LinkMenuItem | ExternalLinkMenuItem | 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; +}; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 25d33566ef..2590e07934 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6484,55 +6484,38 @@ a.status-card { } .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-width: 80vw; - .actions-modal__item-label { - font-weight: 500; - } - ul { overflow-y: auto; flex-shrink: 0; - max-height: 80vh; + padding-bottom: 8px; + } - &.with-status { - max-height: calc(80vh - 75px); - } + a, + 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 { - margin: 0; - } - - 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; - } - } + &:hover, + &:active, + &:focus { + background: var(--dropdown-border-color); } } }