diff --git a/app/javascript/mastodon/components/column_header.jsx b/app/javascript/mastodon/components/column_header.jsx
deleted file mode 100644
index 42183f336db..00000000000
--- a/app/javascript/mastodon/components/column_header.jsx
+++ /dev/null
@@ -1,233 +0,0 @@
-import PropTypes from 'prop-types';
-import { PureComponent, useCallback } from 'react';
-
-import { FormattedMessage, injectIntl, defineMessages, useIntl } from 'react-intl';
-
-import classNames from 'classnames';
-import { withRouter } from 'react-router-dom';
-
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react';
-import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
-import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
-import CloseIcon from '@/material-icons/400-24px/close.svg?react';
-import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
-import { Icon } from 'mastodon/components/icon';
-import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
-import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
-import { WithRouterPropTypes } from 'mastodon/utils/react_router';
-
-
-import { useAppHistory } from './router';
-
-const messages = defineMessages({
- show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
- hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
- moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
- moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
- back: { id: 'column_back_button.label', defaultMessage: 'Back' },
-});
-
-const BackButton = ({ onlyIcon }) => {
- const history = useAppHistory();
- const intl = useIntl();
-
- const handleBackClick = useCallback(() => {
- if (history.location?.state?.fromMastodon) {
- history.goBack();
- } else {
- history.push('/');
- }
- }, [history]);
-
- return (
-
- );
-};
-
-BackButton.propTypes = {
- onlyIcon: PropTypes.bool,
-};
-
-class ColumnHeader extends PureComponent {
- static propTypes = {
- identity: identityContextPropShape,
- intl: PropTypes.object.isRequired,
- title: PropTypes.node,
- icon: PropTypes.string,
- iconComponent: PropTypes.func,
- active: PropTypes.bool,
- multiColumn: PropTypes.bool,
- extraButton: PropTypes.node,
- showBackButton: PropTypes.bool,
- children: PropTypes.node,
- pinned: PropTypes.bool,
- placeholder: PropTypes.bool,
- onPin: PropTypes.func,
- onMove: PropTypes.func,
- onClick: PropTypes.func,
- appendContent: PropTypes.node,
- collapseIssues: PropTypes.bool,
- ...WithRouterPropTypes,
- };
-
- state = {
- collapsed: true,
- animating: false,
- };
-
- handleToggleClick = (e) => {
- e.stopPropagation();
- this.setState({ collapsed: !this.state.collapsed, animating: true });
- };
-
- handleTitleClick = () => {
- this.props.onClick?.();
- };
-
- handleMoveLeft = () => {
- this.props.onMove(-1);
- };
-
- handleMoveRight = () => {
- this.props.onMove(1);
- };
-
- handleTransitionEnd = () => {
- this.setState({ animating: false });
- };
-
- handlePin = () => {
- if (!this.props.pinned) {
- this.props.history.replace('/');
- }
-
- this.props.onPin();
- };
-
- render () {
- const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
- const { collapsed, animating } = this.state;
-
- const wrapperClassName = classNames('column-header__wrapper', {
- 'active': active,
- });
-
- const buttonClassName = classNames('column-header', {
- 'active': active,
- });
-
- const collapsibleClassName = classNames('column-header__collapsible', {
- 'collapsed': collapsed,
- 'animating': animating,
- });
-
- const collapsibleButtonClassName = classNames('column-header__button', {
- 'active': !collapsed,
- });
-
- let extraContent, pinButton, moveButtons, backButton, collapseButton;
-
- if (children) {
- extraContent = (
-
- {children}
-
- );
- }
-
- if (multiColumn && pinned) {
- pinButton = ;
-
- moveButtons = (
-
-
-
-
- );
- } else if (multiColumn && this.props.onPin) {
- pinButton = ;
- }
-
- if (history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
- backButton = ;
- }
-
- const collapsedContent = [
- extraContent,
- ];
-
- if (multiColumn) {
- collapsedContent.push(
-
- {pinButton}
- {moveButtons}
-
- );
- }
-
- if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
- collapseButton = (
-
- );
- }
-
- const hasTitle = (icon || iconComponent) && title;
-
- const component = (
-
-
- {hasTitle && (
- <>
- {backButton}
-
-
- >
- )}
-
- {!hasTitle && backButton}
-
-
- {extraButton}
- {collapseButton}
-
-
-
-
-
- {(!collapsed || animating) && collapsedContent}
-
-
-
- {appendContent}
-
- );
-
- if (placeholder) {
- return component;
- } else {
- return (
- {component}
- );
- }
- }
-
-}
-
-export default injectIntl(withIdentity(withRouter(ColumnHeader)));
diff --git a/app/javascript/mastodon/components/column_header.tsx b/app/javascript/mastodon/components/column_header.tsx
new file mode 100644
index 00000000000..ec946cab3ed
--- /dev/null
+++ b/app/javascript/mastodon/components/column_header.tsx
@@ -0,0 +1,301 @@
+import { useCallback, useState } from 'react';
+
+import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
+
+import classNames from 'classnames';
+
+import AddIcon from '@/material-icons/400-24px/add.svg?react';
+import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react';
+import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
+import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
+import CloseIcon from '@/material-icons/400-24px/close.svg?react';
+import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
+import type { IconProp } from 'mastodon/components/icon';
+import { Icon } from 'mastodon/components/icon';
+import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
+import { useIdentity } from 'mastodon/identity_context';
+
+import { useAppHistory } from './router';
+
+const messages = defineMessages({
+ show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
+ hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
+ moveLeft: {
+ id: 'column_header.moveLeft_settings',
+ defaultMessage: 'Move column to the left',
+ },
+ moveRight: {
+ id: 'column_header.moveRight_settings',
+ defaultMessage: 'Move column to the right',
+ },
+ back: { id: 'column_back_button.label', defaultMessage: 'Back' },
+});
+
+const BackButton: React.FC<{
+ onlyIcon: boolean;
+}> = ({ onlyIcon }) => {
+ const history = useAppHistory();
+ const intl = useIntl();
+
+ const handleBackClick = useCallback(() => {
+ if (history.location.state?.fromMastodon) {
+ history.goBack();
+ } else {
+ history.push('/');
+ }
+ }, [history]);
+
+ return (
+
+ );
+};
+
+export interface Props {
+ title?: string;
+ icon?: string;
+ iconComponent?: IconProp;
+ active?: boolean;
+ children?: React.ReactNode;
+ pinned?: boolean;
+ multiColumn?: boolean;
+ extraButton?: React.ReactNode;
+ showBackButton?: boolean;
+ placeholder?: boolean;
+ appendContent?: React.ReactNode;
+ collapseIssues?: boolean;
+ onClick?: () => void;
+ onMove?: (arg0: number) => void;
+ onPin?: () => void;
+}
+
+export const ColumnHeader: React.FC = ({
+ title,
+ icon,
+ iconComponent,
+ active,
+ children,
+ pinned,
+ multiColumn,
+ extraButton,
+ showBackButton,
+ placeholder,
+ appendContent,
+ collapseIssues,
+ onClick,
+ onMove,
+ onPin,
+}) => {
+ const intl = useIntl();
+ const { signedIn } = useIdentity();
+ const history = useAppHistory();
+ const [collapsed, setCollapsed] = useState(true);
+ const [animating, setAnimating] = useState(false);
+
+ const handleToggleClick = useCallback(
+ (e: React.MouseEvent) => {
+ e.stopPropagation();
+ setCollapsed((value) => !value);
+ setAnimating(true);
+ },
+ [setCollapsed, setAnimating],
+ );
+
+ const handleTitleClick = useCallback(() => {
+ onClick?.();
+ }, [onClick]);
+
+ const handleMoveLeft = useCallback(() => {
+ onMove?.(-1);
+ }, [onMove]);
+
+ const handleMoveRight = useCallback(() => {
+ onMove?.(1);
+ }, [onMove]);
+
+ const handleTransitionEnd = useCallback(() => {
+ setAnimating(false);
+ }, [setAnimating]);
+
+ const handlePin = useCallback(() => {
+ if (!pinned) {
+ history.replace('/');
+ }
+
+ onPin?.();
+ }, [history, pinned, onPin]);
+
+ const wrapperClassName = classNames('column-header__wrapper', {
+ active,
+ });
+
+ const buttonClassName = classNames('column-header', {
+ active,
+ });
+
+ const collapsibleClassName = classNames('column-header__collapsible', {
+ collapsed,
+ animating,
+ });
+
+ const collapsibleButtonClassName = classNames('column-header__button', {
+ active: !collapsed,
+ });
+
+ let extraContent, pinButton, moveButtons, backButton, collapseButton;
+
+ if (children) {
+ extraContent = (
+
+ {children}
+
+ );
+ }
+
+ if (multiColumn && pinned) {
+ pinButton = (
+
+ );
+
+ moveButtons = (
+
+
+
+
+ );
+ } else if (multiColumn && onPin) {
+ pinButton = (
+
+ );
+ }
+
+ if (
+ !pinned &&
+ ((multiColumn && history.location.state?.fromMastodon) || showBackButton)
+ ) {
+ backButton = ;
+ }
+
+ const collapsedContent = [extraContent];
+
+ if (multiColumn) {
+ collapsedContent.push(
+
+ {pinButton}
+ {moveButtons}
+
,
+ );
+ }
+
+ if (signedIn && (children || (multiColumn && onPin))) {
+ collapseButton = (
+
+ );
+ }
+
+ const hasIcon = icon && iconComponent;
+ const hasTitle = hasIcon && title;
+
+ const component = (
+
+
+ {hasTitle && (
+ <>
+ {backButton}
+
+
+ >
+ )}
+
+ {!hasTitle && backButton}
+
+
+ {extraButton}
+ {collapseButton}
+
+
+
+
+
+ {(!collapsed || animating) && collapsedContent}
+
+
+
+ {appendContent}
+
+ );
+
+ if (placeholder) {
+ return component;
+ } else {
+ return {component};
+ }
+};
+
+// eslint-disable-next-line import/no-default-export
+export default ColumnHeader;
diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx
index 482c6858f48..51d283a482d 100644
--- a/app/javascript/mastodon/features/directory/index.tsx
+++ b/app/javascript/mastodon/features/directory/index.tsx
@@ -16,7 +16,7 @@ import {
} from 'mastodon/actions/columns';
import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
import Column from 'mastodon/components/column';
-import ColumnHeader from 'mastodon/components/column_header';
+import { ColumnHeader } from 'mastodon/components/column_header';
import { LoadMore } from 'mastodon/components/load_more';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button';
@@ -86,7 +86,7 @@ export const Directory: React.FC<{
}, [dispatch, order, local]);
const handleMove = useCallback(
- (dir: string) => {
+ (dir: number) => {
dispatch(moveColumn(columnId, dir));
},
[dispatch, columnId],
@@ -185,7 +185,6 @@ export const Directory: React.FC<{
label={intl.formatMessage(messages.title)}
>
= (otherProps) => (
+export const ColumnLoading: React.FC = (otherProps) => (