From 152de5e8e8d3e3adfa3a9703fe86f39fc46220e2 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 21 Sep 2026 12:56:38 +0000 Subject: [PATCH] Redesign: Fix "Skip to main content" link (#40626) --- .../navigation_panel/redesign/header.tsx | 8 +++++- .../redesign_multicol.module.scss | 10 +++++++ .../ui/components/skip_links/index.tsx | 28 +++++++++++++++++-- .../skip_links/skip_links.module.scss | 2 +- app/javascript/mastodon/features/ui/index.jsx | 1 + .../mastodon/features/ui/util/focusUtils.ts | 15 ++++++++++ 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/features/navigation_panel/redesign/header.tsx b/app/javascript/mastodon/features/navigation_panel/redesign/header.tsx index bf0b1d6b90f..09ea3fb424c 100644 --- a/app/javascript/mastodon/features/navigation_panel/redesign/header.tsx +++ b/app/javascript/mastodon/features/navigation_panel/redesign/header.tsx @@ -6,6 +6,8 @@ import { Link } from 'react-router-dom'; import { IconLogo } from '@/mastodon/components/logo'; import { customAppIcon, domain, title } from '@/mastodon/initial_state'; +import { getNavigationSkipLinkId } from '../../ui/components/skip_links'; + import classes from './header.module.scss'; export const NavigationHeader: React.FC<{ @@ -24,7 +26,11 @@ export const LogoLockup: React.FC<{ className?: string; }> = ({ siteName, className }) => { return ( - + {customAppIcon && ( )} diff --git a/app/javascript/mastodon/features/ui/components/columns_area/redesign_multicol.module.scss b/app/javascript/mastodon/features/ui/components/columns_area/redesign_multicol.module.scss index a23e9d53b87..170e85ace1f 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area/redesign_multicol.module.scss +++ b/app/javascript/mastodon/features/ui/components/columns_area/redesign_multicol.module.scss @@ -11,6 +11,16 @@ gap: var(--multi-column-gap); padding-inline-end: var(--multi-column-gap); overflow-x: auto; + scroll-padding-inline-start: var(--multi-column-navigation-width); + + &:focus-visible { + // Scrollable elements are made focusable by some browsers, this adds + // a visible outline in this case. Using `border` instead of `outline` + // causes a light layout jump shift on focus, but an outline would be + // rendered below the navbar and columns and didn't look right. + border: var(--outline-focus-default); + border-radius: var(--radius-2); + } } .navigationWrapper { diff --git a/app/javascript/mastodon/features/ui/components/skip_links/index.tsx b/app/javascript/mastodon/features/ui/components/skip_links/index.tsx index e5a74ba992b..f297f52bb86 100644 --- a/app/javascript/mastodon/features/ui/components/skip_links/index.tsx +++ b/app/javascript/mastodon/features/ui/components/skip_links/index.tsx @@ -2,15 +2,16 @@ import { useCallback, useId } from 'react'; import { useIntl } from 'react-intl'; +import { isRedesignEnabled } from '@/mastodon/utils/environment'; import { useAppSelector } from 'mastodon/store'; import classes from './skip_links.module.scss'; export const getNavigationSkipLinkId = () => 'skip-link-target-nav'; -export const getColumnSkipLinkId = (index: number) => - `skip-link-target-content-${index}`; +export const getColumnSkipLinkId = (index: number | null) => + `skip-link-target-content-${index ?? ''}`; -export const SkipLinks: React.FC<{ +const LegacySkipLinks: React.FC<{ multiColumn: boolean; onFocusGettingStartedColumn: () => void; }> = ({ multiColumn, onFocusGettingStartedColumn }) => { @@ -54,6 +55,23 @@ export const SkipLinks: React.FC<{ ); }; +const RedesignSkipLinks: React.FC = () => { + const intl = useIntl(); + + return ( +
+
+ + {intl.formatMessage({ + id: 'skip_links.skip_to_content', + defaultMessage: 'Skip to main content', + })} + +
+
+ ); +}; + const SkipLink: React.FC<{ children: string; target: string; @@ -82,3 +100,7 @@ const SkipLink: React.FC<{ ); }; + +export const SkipLinks = isRedesignEnabled() + ? RedesignSkipLinks + : LegacySkipLinks; diff --git a/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss b/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss index ad206dfecc5..580231d468a 100644 --- a/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss +++ b/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss @@ -1,6 +1,6 @@ .list { position: fixed; - z-index: 100; + z-index: 200; margin: 10px; padding: 10px 16px; border-radius: 10px; diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index d2eb635f634..75f84049128 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -649,6 +649,7 @@ class UI extends PureComponent {
{!minimalShell && ( diff --git a/app/javascript/mastodon/features/ui/util/focusUtils.ts b/app/javascript/mastodon/features/ui/util/focusUtils.ts index 6e6b8e24e09..3d566947dde 100644 --- a/app/javascript/mastodon/features/ui/util/focusUtils.ts +++ b/app/javascript/mastodon/features/ui/util/focusUtils.ts @@ -1,3 +1,5 @@ +import { isRedesignEnabled } from '@/mastodon/utils/environment'; + import { getColumnSkipLinkId, getNavigationSkipLinkId, @@ -45,11 +47,24 @@ function focusColumnTitle(index: number, multiColumn: boolean) { } } +function focusRedesignColumnTitle(index: number) { + const idToFocus = + index === 1 ? getNavigationSkipLinkId() : getColumnSkipLinkId(index - 1); + + document.querySelector(`#${idToFocus}`)?.focus(); +} + /** * Move focus to the column of the passed index (1-based). * Focus is placed on the topmost visible item, or the column title. */ export function focusColumn(index = 1) { + if (isRedesignEnabled()) { + // In the redesign, always focus the column title + focusRedesignColumnTitle(index); + return; + } + // Skip the leftmost drawer in multi-column mode const isMultiColumnLayout = !!document.querySelector( 'body.layout-multiple-columns',