diff --git a/app/javascript/mastodon/components/empty_state/empty_state.module.scss b/app/javascript/mastodon/components/empty_state/empty_state.module.scss
index 64d9a4e584..cb2332aec6 100644
--- a/app/javascript/mastodon/components/empty_state/empty_state.module.scss
+++ b/app/javascript/mastodon/components/empty_state/empty_state.module.scss
@@ -13,8 +13,7 @@
.content {
max-width: 370px;
- svg,
- img {
+ :where(svg, img) {
width: 200px;
aspect-ratio: 1;
object-fit: contain;
@@ -22,16 +21,10 @@
margin-bottom: 16px;
}
- h3 {
- font-size: 17px;
- font-weight: 500;
- text-wrap: balance;
- line-height: 1.2;
- }
-
p {
margin-top: 8px;
font-size: 15px;
+ line-height: 1.4;
color: var(--color-text-secondary);
text-wrap: pretty;
}
@@ -41,6 +34,18 @@
}
}
+.heading {
+ font-size: 17px;
+ font-weight: 500;
+ text-wrap: balance;
+ line-height: 1.2;
+}
+
+.errorImage {
+ width: 280px;
+ margin: -10% 0;
+}
+
[data-color-scheme='dark'] .defaultImage {
--color-skin-1: #3a3a50;
--color-skin-2: #67678e;
diff --git a/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx b/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx
index 83fce03468..c1faaf6f39 100644
--- a/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx
+++ b/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx
@@ -29,6 +29,14 @@ export const Default: Story = {
},
};
+export const Error: Story = {
+ args: {
+ image: 'error',
+ title: 'Error',
+ message: 'Something went wrong loading the page.',
+ },
+};
+
export const WithAction: Story = {
args: {
...Default.args,
diff --git a/app/javascript/mastodon/components/empty_state/index.tsx b/app/javascript/mastodon/components/empty_state/index.tsx
index e332aaedb5..3f0738ce5a 100644
--- a/app/javascript/mastodon/components/empty_state/index.tsx
+++ b/app/javascript/mastodon/components/empty_state/index.tsx
@@ -4,10 +4,15 @@ import classNames from 'classnames';
import ElephantImage from '@/images/elephant_ui.svg?react';
+import { GIF } from '../gif';
+
import classes from './empty_state.module.scss';
const images = {
default: ,
+ error: (
+
+ ),
};
/**
@@ -21,6 +26,7 @@ export const EmptyState: React.FC<{
title?: React.ReactNode;
message?: React.ReactNode;
children?: React.ReactNode;
+ headingLevel?: 'h2' | 'h3' | 'h4';
className?: string;
}> = ({
image = 'default',
@@ -29,6 +35,7 @@ export const EmptyState: React.FC<{
),
message,
children,
+ headingLevel: Heading = 'h2',
className,
}) => {
const imageToRender = typeof image === 'string' ? images[image] : image;
@@ -38,7 +45,7 @@ export const EmptyState: React.FC<{
{(title || message || imageToRender) && (
{imageToRender}
- {!!title &&
{title}
}
+ {!!title &&
{title}}
{!!message &&
{message}
}
)}
diff --git a/app/javascript/mastodon/components/gif.tsx b/app/javascript/mastodon/components/gif.tsx
index 1cc0881a5a..7bce985784 100644
--- a/app/javascript/mastodon/components/gif.tsx
+++ b/app/javascript/mastodon/components/gif.tsx
@@ -4,7 +4,7 @@ import { autoPlayGif } from 'mastodon/initial_state';
export const GIF: React.FC<{
src: string;
staticSrc: string;
- className: string;
+ className?: string;
animate?: boolean;
}> = ({ src, staticSrc, className, animate = autoPlayGif }) => {
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
diff --git a/app/javascript/mastodon/features/account_edit/components/column.tsx b/app/javascript/mastodon/features/account_edit/components/column.tsx
index a9b0f8cbd5..2718aab7f2 100644
--- a/app/javascript/mastodon/features/account_edit/components/column.tsx
+++ b/app/javascript/mastodon/features/account_edit/components/column.tsx
@@ -9,7 +9,7 @@ import { Helmet } from '@unhead/react/helmet';
import { Column } from '@/mastodon/components/column';
import { ColumnHeader } from '@/mastodon/components/column_header';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
-import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
+import { BundleColumnError } from '@/mastodon/features/ui/components/bundle_column_error';
import { useColumnsContext } from '../../ui/util/columns_context';
import classes from '../styles.module.scss';
diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx
index 3082f19abc..bccdddeda8 100644
--- a/app/javascript/mastodon/features/account_featured/index.tsx
+++ b/app/javascript/mastodon/features/account_featured/index.tsx
@@ -19,7 +19,7 @@ import {
} from '@/mastodon/components/scrollable_list/components';
import type { TruncatedListItemInfo } from '@/mastodon/components/truncated_list';
import { TruncatedListItems } from '@/mastodon/components/truncated_list';
-import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
+import { BundleColumnError } from '@/mastodon/features/ui/components/bundle_column_error';
import Column from '@/mastodon/features/ui/components/column';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAccountId } from '@/mastodon/hooks/useAccountId';
diff --git a/app/javascript/mastodon/features/account_gallery/index.tsx b/app/javascript/mastodon/features/account_gallery/index.tsx
index feba6f454d..25ad8c88c1 100644
--- a/app/javascript/mastodon/features/account_gallery/index.tsx
+++ b/app/javascript/mastodon/features/account_gallery/index.tsx
@@ -11,7 +11,7 @@ import { ColumnBackButton } from '@/mastodon/components/column_back_button';
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
import { RemoteHint } from '@/mastodon/components/remote_hint';
import ScrollableList from '@/mastodon/components/scrollable_list';
-import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
+import { BundleColumnError } from '@/mastodon/features/ui/components/bundle_column_error';
import Column from '@/mastodon/features/ui/components/column';
import { useAccountId } from '@/mastodon/hooks/useAccountId';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
diff --git a/app/javascript/mastodon/features/account_timeline/index.tsx b/app/javascript/mastodon/features/account_timeline/index.tsx
index ce1e99a45f..450236386a 100644
--- a/app/javascript/mastodon/features/account_timeline/index.tsx
+++ b/app/javascript/mastodon/features/account_timeline/index.tsx
@@ -19,7 +19,7 @@ import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { RemoteHint } from '@/mastodon/components/remote_hint';
import StatusList from '@/mastodon/components/status_list';
-import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
+import { BundleColumnError } from '@/mastodon/features/ui/components/bundle_column_error';
import {
useAccountId,
useCurrentAccountId,
diff --git a/app/javascript/mastodon/features/followers/components/list.tsx b/app/javascript/mastodon/features/followers/components/list.tsx
index 6c600d570f..3c593e10b8 100644
--- a/app/javascript/mastodon/features/followers/components/list.tsx
+++ b/app/javascript/mastodon/features/followers/components/list.tsx
@@ -6,7 +6,7 @@ import { Column } from '@/mastodon/components/column';
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import ScrollableList from '@/mastodon/components/scrollable_list';
-import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
+import { BundleColumnError } from '@/mastodon/features/ui/components/bundle_column_error';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import { useLayout } from '@/mastodon/hooks/useLayout';
diff --git a/app/javascript/mastodon/features/terms_of_service/index.tsx b/app/javascript/mastodon/features/terms_of_service/index.tsx
index 669fb18b92..0a7c2d5669 100644
--- a/app/javascript/mastodon/features/terms_of_service/index.tsx
+++ b/app/javascript/mastodon/features/terms_of_service/index.tsx
@@ -14,7 +14,7 @@ import { Helmet } from '@unhead/react/helmet';
import { apiGetTermsOfService } from 'mastodon/api/instance';
import type { ApiTermsOfServiceJSON } from 'mastodon/api_types/instance';
import { Column } from 'mastodon/components/column';
-import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
+import { BundleColumnError } from 'mastodon/features/ui/components/bundle_column_error';
const messages = defineMessages({
title: { id: 'terms_of_service.title', defaultMessage: 'Terms of Service' },
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx
deleted file mode 100644
index 81fb8f48c7..0000000000
--- a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import PropTypes from 'prop-types';
-import { PureComponent } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-import { Helmet } from '@unhead/react/helmet';
-import { Link } from 'react-router-dom';
-
-import { Button } from 'mastodon/components/button';
-import Column from 'mastodon/components/column';
-import { injectIntl } from '@/mastodon/components/intl';
-import { GIF } from 'mastodon/components/gif';
-
-class CopyButton extends PureComponent {
-
- static propTypes = {
- children: PropTypes.node.isRequired,
- value: PropTypes.string.isRequired,
- };
-
- state = {
- copied: false,
- };
-
- handleClick = () => {
- const { value } = this.props;
- navigator.clipboard.writeText(value);
- this.setState({ copied: true });
- this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
- };
-
- componentWillUnmount () {
- if (this.timeout) clearTimeout(this.timeout);
- }
-
- render () {
- const { children } = this.props;
- const { copied } = this.state;
-
- return (
-
- );
- }
-
-}
-
-class BundleColumnError extends PureComponent {
-
- static propTypes = {
- errorType: PropTypes.oneOf(['routing', 'network', 'error']),
- onRetry: PropTypes.func,
- intl: PropTypes.object.isRequired,
- multiColumn: PropTypes.bool,
- stacktrace: PropTypes.string,
- };
-
- static defaultProps = {
- errorType: 'routing',
- };
-
- handleRetry = () => {
- const { onRetry } = this.props;
-
- if (onRetry) {
- onRetry();
- }
- };
-
- render () {
- const { errorType, multiColumn, stacktrace } = this.props;
-
- let title, body;
-
- switch(errorType) {
- case 'routing':
- title = ;
- body = ;
- break;
- case 'network':
- title = ;
- body = ;
- break;
- case 'error':
- title = ;
- body = ;
- break;
- }
-
- return (
-
-
-
-
-
-
{title}
-
{body}
-
-
- {errorType === 'network' && }
- {errorType === 'error' && }
-
-
-
-
-
-
-
-
-
- );
- }
-
-}
-
-export default injectIntl(BundleColumnError);
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error/index.tsx b/app/javascript/mastodon/features/ui/components/bundle_column_error/index.tsx
new file mode 100644
index 0000000000..f4acb3059f
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/bundle_column_error/index.tsx
@@ -0,0 +1,122 @@
+import { FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+import { Link } from 'react-router-dom';
+
+import { Helmet } from '@unhead/react/helmet';
+
+import { CopyButton } from '@/mastodon/components/copy_button';
+import { EmptyState } from '@/mastodon/components/empty_state';
+import { Button } from 'mastodon/components/button';
+import { Column } from 'mastodon/components/column';
+
+import classes from './styles.module.scss';
+
+interface BundleColumnErrorProps {
+ errorType?: 'routing' | 'network' | 'error';
+ onRetry?: () => void;
+ multiColumn?: boolean;
+ stacktrace?: string;
+}
+
+export const BundleColumnError: React.FC = ({
+ errorType = 'routing',
+ onRetry,
+ multiColumn,
+ stacktrace,
+}) => {
+ let title, body;
+
+ switch (errorType) {
+ case 'routing':
+ title = (
+
+ );
+ body = (
+
+ );
+ break;
+ case 'network':
+ title = (
+
+ );
+ body = (
+
+ );
+ break;
+ case 'error':
+ title = (
+
+ );
+ body = (
+
+ );
+ break;
+ }
+
+ return (
+
+
+
+ {errorType === 'network' && onRetry && (
+
+ )}
+ {errorType === 'error' && stacktrace && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default BundleColumnError;
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error/styles.module.scss b/app/javascript/mastodon/features/ui/components/bundle_column_error/styles.module.scss
new file mode 100644
index 0000000000..62549efffc
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/bundle_column_error/styles.module.scss
@@ -0,0 +1,12 @@
+.error {
+ flex-grow: 1;
+ border: 1px solid var(--color-border-primary);
+ border-radius: 4px;
+}
+
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 12px;
+ justify-content: center;
+}
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.tsx b/app/javascript/mastodon/features/ui/components/columns_area.tsx
index f5ab3de849..cd94f34ec0 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.tsx
+++ b/app/javascript/mastodon/features/ui/components/columns_area.tsx
@@ -33,7 +33,7 @@ import {
import { useColumnsContext } from '../util/columns_context';
import Bundle from './bundle';
-import BundleColumnError from './bundle_column_error';
+import { BundleColumnError } from './bundle_column_error';
import { ColumnLoading } from './column_loading';
import { ComposePanel, RedirectToMobileComposeIfNeeded } from './compose_panel';
import DrawerLoading from './drawer_loading';
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 809634e8f5..a2771815ab 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3374,8 +3374,7 @@ a.account__display-name {
.column-header,
.column-back-button,
- .scrollable,
- .error-column {
+ .scrollable {
border-radius: 0 !important;
}
@@ -5288,51 +5287,6 @@ a.status-card {
margin-bottom: 0;
}
-.error-column {
- padding: 20px;
- border: 1px solid var(--color-border-primary);
- border-radius: 4px;
- display: flex;
- flex: 1 1 auto;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- cursor: default;
-
- &__image {
- width: 70%;
- max-width: 350px;
- margin-top: -50px;
- }
-
- &__message {
- text-align: center;
- color: var(--color-text-secondary);
- font-size: 15px;
- line-height: 22px;
-
- h1 {
- font-size: 28px;
- line-height: 33px;
- font-weight: 700;
- margin-bottom: 15px;
- color: var(--color-text-primary);
- }
-
- p {
- max-width: 48ch;
- }
-
- &__actions {
- margin-top: 30px;
- display: flex;
- gap: 10px;
- align-items: center;
- justify-content: center;
- }
- }
-}
-
@keyframes heartbeat {
0% {
transform: scale(1);