From 3957b4057254ca5a0555eb73f72d7c2fc10e6d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Tue, 17 Mar 2026 18:27:30 +0100 Subject: [PATCH] nicolium: add skip links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../nicolium/src/components/ui/form-group.tsx | 21 +++++++++---- .../nicolium/src/components/ui/layout.tsx | 1 + packages/nicolium/src/features/ui/index.tsx | 31 ++++++++++++++++--- packages/nicolium/src/locales/en.json | 1 + packages/nicolium/src/styles/new/layout.scss | 19 ++++++++++++ 5 files changed, 62 insertions(+), 11 deletions(-) diff --git a/packages/nicolium/src/components/ui/form-group.tsx b/packages/nicolium/src/components/ui/form-group.tsx index cfd48a34d..50093acc9 100644 --- a/packages/nicolium/src/components/ui/form-group.tsx +++ b/packages/nicolium/src/components/ui/form-group.tsx @@ -24,11 +24,12 @@ const FormGroup: React.FC = (props) => { let firstChild; if (React.isValidElement(inputChildren[0])) { - firstChild = React.cloneElement( - inputChildren[0], + firstChild = React.cloneElement(inputChildren[0], { // @ts-expect-error - { id: formFieldId }, - ); + id: formFieldId, + 'aria-invalid': hasError, + 'aria-describedby': hasError ? `error-${formFieldId}` : undefined, + }); } // @ts-expect-error @@ -53,7 +54,11 @@ const FormGroup: React.FC = (props) => { {hasError && (
-

+

{errors.join(', ')}

@@ -96,7 +101,11 @@ const FormGroup: React.FC = (props) => { {inputChildren.filter((_, i) => i !== 0)} {hasError && ( -

+

{errors.join(', ')}

)} diff --git a/packages/nicolium/src/components/ui/layout.tsx b/packages/nicolium/src/components/ui/layout.tsx index 88bfc1a88..4005d4fdb 100644 --- a/packages/nicolium/src/components/ui/layout.tsx +++ b/packages/nicolium/src/components/ui/layout.tsx @@ -139,6 +139,7 @@ const Main: React.FC> = ({ children, classN }, className, )} + tabIndex={-1} > {children} diff --git a/packages/nicolium/src/features/ui/index.tsx b/packages/nicolium/src/features/ui/index.tsx index bcb315c0a..cd0393b55 100644 --- a/packages/nicolium/src/features/ui/index.tsx +++ b/packages/nicolium/src/features/ui/index.tsx @@ -2,6 +2,7 @@ import { Outlet, useNavigate } from '@tanstack/react-router'; import clsx from 'clsx'; import React, { Suspense, useEffect, useRef } from 'react'; import { Toaster } from 'react-hot-toast'; +import { FormattedMessage } from 'react-intl'; import { register as registerPushNotifications } from '@/actions/push-notifications/registerer'; import SidebarNavigation from '@/components/navigation/sidebar-navigation'; @@ -22,11 +23,8 @@ import { usePrefetchNotifications } from '@/queries/notifications/use-notificati import { useFilters } from '@/queries/settings/use-filters'; import { scheduledStatusesQueryOptions } from '@/queries/statuses/scheduled-statuses'; import { useAuthStore } from '@/stores/auth'; -import { useInstance } from '@/stores/instance'; -import { useInstanceStore } from '@/stores/instance'; -// 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/statuses/status'; +import { useInstance, useInstanceStore } from '@/stores/instance'; +import { useModalsActions } from '@/stores/modals'; import { useShoutboxSubscription } from '@/stores/shoutbox'; import { useIsDropdownMenuOpen } from '@/stores/ui'; import { useIsStandalone } from '@/utils/state'; @@ -39,6 +37,9 @@ import { StatusHoverCard, } from './util/async-components'; import GlobalHotkeys from './util/global-hotkeys'; +// 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/statuses/status'; const UI: React.FC = React.memo(() => { const navigate = useNavigate(); @@ -50,6 +51,7 @@ const UI: React.FC = React.memo(() => { const vapidKey = useAuthStore((state) => state.app?.vapid_key) ?? instance.configuration.vapid.public_key; const client = useClient(); + const { openModal } = useModalsActions(); const isDropdownMenuOpen = useIsDropdownMenuOpen(); const standalone = useIsStandalone(); @@ -84,6 +86,14 @@ const UI: React.FC = React.memo(() => { e.preventDefault(); }; + const handleSkipToContent = () => { + document.querySelector('main')?.focus(); + }; + + const handleOpenHotkeysModal = () => { + openModal('HOTKEYS'); + }; + /** Load initial data when a user is logged in */ const loadAccountData = () => { if (!account) return; @@ -150,6 +160,17 @@ const UI: React.FC = React.memo(() => { return (
+
+ + +