diff --git a/app/javascript/flavours/glitch/features/onboarding/index.jsx b/app/javascript/flavours/glitch/features/onboarding/index.jsx
index 129dd0da3e..2729e760f2 100644
--- a/app/javascript/flavours/glitch/features/onboarding/index.jsx
+++ b/app/javascript/flavours/glitch/features/onboarding/index.jsx
@@ -48,7 +48,6 @@ class Onboarding extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
account: ImmutablePropTypes.record,
- multiColumn: PropTypes.bool,
...WithRouterPropTypes,
};
@@ -101,14 +100,14 @@ class Onboarding extends ImmutablePureComponent {
}
render () {
- const { account, multiColumn } = this.props;
+ const { account } = this.props;
const { step, shareClicked } = this.state;
switch(step) {
case 'follows':
- return
;
+ return
;
case 'share':
- return
;
+ return
;
}
return (
diff --git a/app/javascript/flavours/glitch/features/onboarding/share.jsx b/app/javascript/flavours/glitch/features/onboarding/share.jsx
index 66c95b8c9b..7c35c9a492 100644
--- a/app/javascript/flavours/glitch/features/onboarding/share.jsx
+++ b/app/javascript/flavours/glitch/features/onboarding/share.jsx
@@ -14,7 +14,7 @@ import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/out
import SwipeableViews from 'react-swipeable-views';
import Column from 'flavours/glitch/components/column';
-import ColumnBackButton from 'flavours/glitch/components/column_back_button';
+import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
import { Icon } from 'flavours/glitch/components/icon';
import { me, domain } from 'flavours/glitch/initial_state';
@@ -146,18 +146,17 @@ class Share extends PureComponent {
static propTypes = {
onBack: PropTypes.func,
account: ImmutablePropTypes.record,
- multiColumn: PropTypes.bool,
intl: PropTypes.object,
};
render () {
- const { onBack, account, multiColumn, intl } = this.props;
+ const { onBack, account, intl } = this.props;
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
return (
-
+
diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx
index fafcb78f1d..b888ab6850 100644
--- a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx
+++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx
@@ -13,7 +13,6 @@ import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outline
import { getStatusList } from 'flavours/glitch/selectors';
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import StatusList from '../../components/status_list';
import Column from '../ui/components/column';
@@ -52,8 +51,7 @@ class PinnedStatuses extends ImmutablePureComponent {
const { intl, statusIds, hasMore, multiColumn } = this.props;
return (
-
-
+
+
);
return (
{
- this.props.onClick();
- };
-
- render () {
- const { icon, iconComponent, type, active, columnHeaderId } = this.props;
- let iconElement = '';
-
- if (icon) {
- iconElement =
;
- }
-
- return (
-
-
-
- );
- }
-
-}
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx
index a37e3cadce..f3e1bfe492 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
-import { Children, cloneElement } from 'react';
+import { Children, cloneElement, useCallback } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -21,6 +21,7 @@ import {
ListTimeline,
Directory,
} from '../util/async-components';
+import { useColumnsContext } from '../util/columns_context';
import BundleColumnError from './bundle_column_error';
import { ColumnLoading } from './column_loading';
@@ -43,6 +44,17 @@ const componentMap = {
'DIRECTORY': Directory,
};
+const TabsBarPortal = () => {
+ const {setTabsBarElement} = useColumnsContext();
+
+ const setRef = useCallback((element) => {
+ if(element)
+ setTabsBarElement(element);
+ }, [setTabsBarElement]);
+
+ return
;
+};
+
export default class ColumnsArea extends ImmutablePureComponent {
static propTypes = {
columns: ImmutablePropTypes.list.isRequired,
@@ -146,7 +158,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx
index fa943f579f..dc3a7ce37b 100644
--- a/app/javascript/flavours/glitch/features/ui/index.jsx
+++ b/app/javascript/flavours/glitch/features/ui/index.jsx
@@ -67,8 +67,8 @@ import {
About,
PrivacyPolicy,
} from './util/async-components';
+import { ColumnsContextProvider } from './util/columns_context';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
-
// Dummy import, to make sure that ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../components/status';
@@ -188,69 +188,71 @@ class SwitchingColumnsArea extends PureComponent {
}
return (
-
-
- {redirect}
+
+
+
+ {redirect}
- {singleColumn ? : null}
- {singleColumn && pathName.startsWith('/deck/') ? : null}
- {/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
- {!singleColumn && pathName === '/getting-started' ? : null}
- {!singleColumn && pathName === '/home' ? : null}
+ {singleColumn ? : null}
+ {singleColumn && pathName.startsWith('/deck/') ? : null}
+ {/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
+ {!singleColumn && pathName === '/getting-started' ? : null}
+ {!singleColumn && pathName === '/home' ? : null}
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- {/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
-
-
-
-
-
+ {/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
);
}
diff --git a/app/javascript/flavours/glitch/features/ui/util/columns_context.tsx b/app/javascript/flavours/glitch/features/ui/util/columns_context.tsx
new file mode 100644
index 0000000000..e02918deb0
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/util/columns_context.tsx
@@ -0,0 +1,51 @@
+import type { ReactElement } from 'react';
+import { createContext, useContext, useMemo, useState } from 'react';
+import { createPortal } from 'react-dom';
+
+export const ColumnsContext = createContext<{
+ tabsBarElement: HTMLElement | null;
+ setTabsBarElement: (element: HTMLElement) => void;
+ multiColumn: boolean;
+}>({
+ tabsBarElement: null,
+ multiColumn: false,
+ setTabsBarElement: () => undefined, // no-op
+});
+
+export function useColumnsContext() {
+ return useContext(ColumnsContext);
+}
+
+export const ButtonInTabsBar: React.FC<{
+ children: ReactElement | string | undefined;
+}> = ({ children }) => {
+ const { multiColumn, tabsBarElement } = useColumnsContext();
+
+ if (multiColumn) {
+ return children;
+ } else if (!tabsBarElement) {
+ return children;
+ } else {
+ return createPortal(children, tabsBarElement);
+ }
+};
+
+type ContextValue = React.ContextType;
+
+export const ColumnsContextProvider: React.FC<
+ React.PropsWithChildren<{ multiColumn: boolean }>
+> = ({ multiColumn, children }) => {
+ const [tabsBarElement, setTabsBarElement] =
+ useState(null);
+
+ const contextValue = useMemo(
+ () => ({ multiColumn, tabsBarElement, setTabsBarElement }),
+ [multiColumn, tabsBarElement],
+ );
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index e210c553bb..5a2962e881 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -218,20 +218,6 @@ $ui-header-height: 55px;
margin-inline-end: 5px;
}
-.column-back-button--slim {
- position: relative;
-}
-
-.column-back-button--slim-button {
- cursor: pointer;
- flex: 0 0 auto;
- font-size: 16px;
- padding: 15px;
- position: absolute;
- inset-inline-end: 0;
- top: -48px;
-}
-
.switch-to-advanced {
color: $light-text-color;
background-color: $ui-base-color;
diff --git a/app/javascript/flavours/glitch/test_helpers.tsx b/app/javascript/flavours/glitch/test_helpers.tsx
new file mode 100644
index 0000000000..6895895569
--- /dev/null
+++ b/app/javascript/flavours/glitch/test_helpers.tsx
@@ -0,0 +1,62 @@
+import PropTypes from 'prop-types';
+import type { PropsWithChildren } from 'react';
+import { Component } from 'react';
+
+import { IntlProvider } from 'react-intl';
+
+import { MemoryRouter } from 'react-router';
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { render as rtlRender } from '@testing-library/react';
+
+class FakeIdentityWrapper extends Component<
+ PropsWithChildren<{ signedIn: boolean }>
+> {
+ static childContextTypes = {
+ identity: PropTypes.shape({
+ signedIn: PropTypes.bool.isRequired,
+ accountId: PropTypes.string,
+ disabledAccountId: PropTypes.string,
+ accessToken: PropTypes.string,
+ }).isRequired,
+ };
+
+ getChildContext() {
+ return {
+ identity: {
+ signedIn: this.props.signedIn,
+ accountId: '123',
+ accessToken: 'test-access-token',
+ },
+ };
+ }
+
+ render() {
+ return this.props.children;
+ }
+}
+
+function render(
+ ui: React.ReactElement,
+ { locale = 'en', signedIn = true, ...renderOptions } = {},
+) {
+ const Wrapper = (props: { children: React.ReactElement }) => {
+ return (
+
+
+
+ {props.children}
+
+
+
+ );
+ };
+ return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
+}
+
+// re-export everything
+// eslint-disable-next-line import/no-extraneous-dependencies
+export * from '@testing-library/react';
+
+// override render method
+export { render };