From fcc4b588f4d66c429aa3b95250b1deb06ba29aed Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Fri, 27 Mar 2026 17:57:48 +0100 Subject: [PATCH 1/4] dbeaver/pro#7251 feat: add skip navigation feature for improved accessibility --- .../src/AppScreen/AppScreenService.ts | 6 ++- .../packages/core-app/src/AppScreen/Main.tsx | 5 ++- .../core-app/src/AppScreen/RightArea.tsx | 4 +- .../src/AppScreen/SkipNavBootstrap.ts | 22 ++++++++++ .../core-app/src/AppScreen/SkipNavLinks.css | 40 +++++++++++++++++++ .../core-app/src/AppScreen/SkipNavLinks.tsx | 35 ++++++++++++++++ .../core-app/src/AppScreen/SkipNavService.ts | 18 +++++++++ webapp/packages/core-app/src/index.ts | 3 +- webapp/packages/core-app/src/locales/en.ts | 3 ++ webapp/packages/core-app/src/locales/fr.ts | 3 ++ webapp/packages/core-app/src/locales/it.ts | 3 ++ webapp/packages/core-app/src/locales/ru.ts | 3 ++ webapp/packages/core-app/src/locales/vi.ts | 3 ++ webapp/packages/core-app/src/locales/zh.ts | 3 ++ webapp/packages/core-app/src/module.ts | 11 ++++- .../core-ui/src/SideBarPanel/SideBarPanel.tsx | 2 +- .../plugin-help/src/PluginBootstrap.ts | 8 +++- .../plugin-help/src/SkipNavShortcutsLink.tsx | 27 +++++++++++++ 18 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts create mode 100644 webapp/packages/core-app/src/AppScreen/SkipNavLinks.css create mode 100644 webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx create mode 100644 webapp/packages/core-app/src/AppScreen/SkipNavService.ts create mode 100644 webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx diff --git a/webapp/packages/core-app/src/AppScreen/AppScreenService.ts b/webapp/packages/core-app/src/AppScreen/AppScreenService.ts index 3e32331549e..c4ce2cd21ce 100644 --- a/webapp/packages/core-app/src/AppScreen/AppScreenService.ts +++ b/webapp/packages/core-app/src/AppScreen/AppScreenService.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -8,6 +8,10 @@ import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { injectable } from '@cloudbeaver/core-di'; +export const PANEL_ID_LEFT_SIDEBAR = 'dbeaver-left-sidebar'; +export const PANEL_ID_RIGHT_SIDEBAR = 'dbeaver-right-sidebar'; +export const PANEL_ID_MAIN_CONTENT = 'dbeaver-main-content'; + @injectable() export class AppScreenService { static screenName = 'app'; diff --git a/webapp/packages/core-app/src/AppScreen/Main.tsx b/webapp/packages/core-app/src/AppScreen/Main.tsx index e243e862528..b655ad0fd4a 100644 --- a/webapp/packages/core-app/src/AppScreen/Main.tsx +++ b/webapp/packages/core-app/src/AppScreen/Main.tsx @@ -24,6 +24,7 @@ import { import { useService } from '@cloudbeaver/core-di'; import { LeftBarPanelService, SideBarPanel, SideBarPanelService, TabStyles } from '@cloudbeaver/core-ui'; +import { PANEL_ID_LEFT_SIDEBAR, PANEL_ID_RIGHT_SIDEBAR } from './AppScreenService.js'; import { RightArea } from './RightArea.js'; import style from './Main.module.css'; import LeftSideBarPanel from './LeftSideBarPanel.module.css'; @@ -67,7 +68,7 @@ export const Main = observer(function Main() { - + @@ -80,7 +81,7 @@ export const Main = observer(function Main() { - + diff --git a/webapp/packages/core-app/src/AppScreen/RightArea.tsx b/webapp/packages/core-app/src/AppScreen/RightArea.tsx index 27598a867c1..ea3242f3ee3 100644 --- a/webapp/packages/core-app/src/AppScreen/RightArea.tsx +++ b/webapp/packages/core-app/src/AppScreen/RightArea.tsx @@ -11,7 +11,7 @@ import { Loader, Pane, Placeholder, ResizerControls, s, SlideDialog, Split, useS import { useService } from '@cloudbeaver/core-di'; import { OptionsPanelService } from '@cloudbeaver/core-ui'; -import { AppScreenService } from './AppScreenService.js'; +import { AppScreenService, PANEL_ID_MAIN_CONTENT } from './AppScreenService.js'; import style from './RightArea.module.css'; interface Props { @@ -33,7 +33,7 @@ export const RightArea = observer(function RightArea({ className }) { } return ( -
+
diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts b/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts new file mode 100644 index 00000000000..9eb16f4bc64 --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts @@ -0,0 +1,22 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; + +import { AppScreenService } from './AppScreenService.js'; +import { SkipNavLinks } from './SkipNavLinks.js'; + +@injectable(() => [AppScreenService]) +export class SkipNavBootstrap extends Bootstrap { + constructor(private readonly appScreenService: AppScreenService) { + super(); + } + + override register(): void { + this.appScreenService.placeholder.add(SkipNavLinks, 0); + } +} diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.css b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.css new file mode 100644 index 00000000000..e96dbc94c38 --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.css @@ -0,0 +1,40 @@ +@layer components { + .dbv-kit-skip-nav { + position: absolute; + top: 8px; + left: 8px; + z-index: 10000; + display: flex; + } + + .dbv-kit-skip-nav__link { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + cursor: pointer; + white-space: nowrap; + clip: rect(0, 0, 0, 0); + clip-path: inset(50%); + padding-inline: var(--dbv-kit-btn-padding-inline); + background: var(--dbv-kit-dialog-content-background); + color: var(--dbv-kit-dialog-content-foreground); + font: inherit; + font-weight: var(--dbv-kit-btn-font-weight); + font-size: var(--dbv-kit-btn-font-size); + border: var(--dbv-kit-btn-border-width) var(--dbv-kit-btn-border-style) var(--dbv-kit-btn-border-color); + border-radius: var(--dbv-kit-btn-border-radius); + box-shadow: var(--dbv-kit-dialog-content-shadow); + } + + .dbv-kit-skip-nav__link:focus { + position: static; + width: auto; + height: var(--dbv-kit-btn-height); + overflow: visible; + clip: auto; + clip-path: none; + outline: var(--dbv-kit-control-outline-width) solid var(--dbv-kit-control-outline-color); + outline-offset: var(--dbv-kit-control-outline-offset); + } +} diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx new file mode 100644 index 00000000000..c3e2e95cbd1 --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx @@ -0,0 +1,35 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { Placeholder, useTranslate } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; + +import { PANEL_ID_LEFT_SIDEBAR, PANEL_ID_MAIN_CONTENT } from './AppScreenService.js'; +import { SkipNavService } from './SkipNavService.js'; +import './SkipNavLinks.css'; + +function focusPanel(panelId: string) { + const element = document.querySelector(`[data-panel-id="${panelId}"]`); + element?.focus(); +} + +export function SkipNavLinks(): React.ReactElement { + const translate = useTranslate(); + const skipNavService = useService(SkipNavService); + + return ( + + ); +} diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavService.ts b/webapp/packages/core-app/src/AppScreen/SkipNavService.ts new file mode 100644 index 00000000000..7c4fe126e46 --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavService.ts @@ -0,0 +1,18 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; +import { injectable } from '@cloudbeaver/core-di'; + +@injectable() +export class SkipNavService { + readonly extraLinks: PlaceholderContainer; + + constructor() { + this.extraLinks = new PlaceholderContainer(); + } +} diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index b44de29398e..480dd488d04 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -10,6 +10,7 @@ import './module.js'; // Services export * from './AppScreen/AppScreenService.js'; export * from './AppScreen/AppScreenBootstrap.js'; +export * from './AppScreen/SkipNavService.js'; export * from './AppLocaleService.js'; diff --git a/webapp/packages/core-app/src/locales/en.ts b/webapp/packages/core-app/src/locales/en.ts index fd517fa08bb..1ef02d5ea6f 100644 --- a/webapp/packages/core-app/src/locales/en.ts +++ b/webapp/packages/core-app/src/locales/en.ts @@ -1,4 +1,7 @@ export default [ + ['app_skip_nav_label', 'Skip navigation'], + ['app_skip_nav_navigator', 'Skip to Navigator'], + ['app_skip_nav_main_content', 'Skip to Main Content'], ['app_shared_settingsMenu_config', 'Configuration'], ['app_shared_settingsMenu_theme', 'Theme'], ['app_shared_settingsMenu_lang', 'Language'], diff --git a/webapp/packages/core-app/src/locales/fr.ts b/webapp/packages/core-app/src/locales/fr.ts index 2309de170c8..6df5c8e1861 100644 --- a/webapp/packages/core-app/src/locales/fr.ts +++ b/webapp/packages/core-app/src/locales/fr.ts @@ -1,4 +1,7 @@ export default [ + ['app_skip_nav_label', 'Sauter la navigation'], + ['app_skip_nav_navigator', 'Aller au navigateur'], + ['app_skip_nav_main_content', 'Aller au contenu principal'], ['app_shared_settingsMenu_config', 'Configuration'], ['app_shared_settingsMenu_theme', 'Thème'], ['app_shared_settingsMenu_lang', 'Langue'], diff --git a/webapp/packages/core-app/src/locales/it.ts b/webapp/packages/core-app/src/locales/it.ts index 00964e5d75d..dc9efc07c9b 100644 --- a/webapp/packages/core-app/src/locales/it.ts +++ b/webapp/packages/core-app/src/locales/it.ts @@ -1,4 +1,7 @@ export default [ + ['app_skip_nav_label', 'Salta la navigazione'], + ['app_skip_nav_navigator', 'Vai al navigatore'], + ['app_skip_nav_main_content', 'Vai al contenuto principale'], ['app_shared_settingsMenu_config', 'Configurazione'], ['app_shared_settingsMenu_theme', 'Tema'], ['app_shared_settingsMenu_lang', 'Lingua'], diff --git a/webapp/packages/core-app/src/locales/ru.ts b/webapp/packages/core-app/src/locales/ru.ts index 909f271442a..559ea5fcac7 100644 --- a/webapp/packages/core-app/src/locales/ru.ts +++ b/webapp/packages/core-app/src/locales/ru.ts @@ -1,4 +1,7 @@ export default [ + ['app_skip_nav_label', 'Быстрая навигация'], + ['app_skip_nav_navigator', 'Перейти к навигатору'], + ['app_skip_nav_main_content', 'Перейти к основному содержимому'], ['app_shared_settingsMenu_config', 'Настройки'], ['app_shared_settingsMenu_theme', 'Тема'], ['app_shared_settingsMenu_lang', 'Язык'], diff --git a/webapp/packages/core-app/src/locales/vi.ts b/webapp/packages/core-app/src/locales/vi.ts index bf995d316c2..0d327b1ee7b 100644 --- a/webapp/packages/core-app/src/locales/vi.ts +++ b/webapp/packages/core-app/src/locales/vi.ts @@ -1,4 +1,7 @@ export default [ + ['app_skip_nav_label', 'Bỏ qua điều hướng'], + ['app_skip_nav_navigator', 'Chuyển đến trình điều hướng'], + ['app_skip_nav_main_content', 'Chuyển đến nội dung chính'], ['app_shared_settingsMenu_config', 'Cấu hình'], ['app_shared_settingsMenu_theme', 'Giao diện'], ['app_shared_settingsMenu_lang', 'Ngôn ngữ'], diff --git a/webapp/packages/core-app/src/locales/zh.ts b/webapp/packages/core-app/src/locales/zh.ts index d66e8f7d82a..4814bc444bf 100644 --- a/webapp/packages/core-app/src/locales/zh.ts +++ b/webapp/packages/core-app/src/locales/zh.ts @@ -1,4 +1,7 @@ export default [ + ['app_skip_nav_label', '跳过导航'], + ['app_skip_nav_navigator', '跳转到导航器'], + ['app_skip_nav_main_content', '跳转到主要内容'], ['app_shared_settingsMenu_config', '配置'], ['app_shared_settingsMenu_theme', '主题'], ['app_shared_settingsMenu_lang', '语言'], diff --git a/webapp/packages/core-app/src/module.ts b/webapp/packages/core-app/src/module.ts index fb93bccd7d7..9aa23932497 100644 --- a/webapp/packages/core-app/src/module.ts +++ b/webapp/packages/core-app/src/module.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -9,12 +9,19 @@ import { Bootstrap, ModuleRegistry } from '@cloudbeaver/core-di'; import { AppScreenService } from './AppScreen/AppScreenService.js'; import { AppScreenBootstrap } from './AppScreen/AppScreenBootstrap.js'; +import { SkipNavBootstrap } from './AppScreen/SkipNavBootstrap.js'; +import { SkipNavService } from './AppScreen/SkipNavService.js'; import { AppLocaleService } from './AppLocaleService.js'; export default ModuleRegistry.add({ name: '@cloudbeaver/core-app', configure: serviceCollection => { - serviceCollection.addSingleton(Bootstrap, AppLocaleService).addSingleton(Bootstrap, AppScreenBootstrap).addSingleton(AppScreenService); + serviceCollection + .addSingleton(Bootstrap, AppLocaleService) + .addSingleton(Bootstrap, AppScreenBootstrap) + .addSingleton(Bootstrap, SkipNavBootstrap) + .addSingleton(AppScreenService) + .addSingleton(SkipNavService); }, }); diff --git a/webapp/packages/core-ui/src/SideBarPanel/SideBarPanel.tsx b/webapp/packages/core-ui/src/SideBarPanel/SideBarPanel.tsx index 704f380d5d2..6d2bd2ae2d0 100644 --- a/webapp/packages/core-ui/src/SideBarPanel/SideBarPanel.tsx +++ b/webapp/packages/core-ui/src/SideBarPanel/SideBarPanel.tsx @@ -51,7 +51,7 @@ export const SideBarPanel = observer(function SideBarPanel({ onChange={tab => selectTab(tab.tabId)} onReorder={onReorder} > -
+
diff --git a/webapp/packages/plugin-help/src/PluginBootstrap.ts b/webapp/packages/plugin-help/src/PluginBootstrap.ts index d624a4637cd..88ead093d83 100644 --- a/webapp/packages/plugin-help/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-help/src/PluginBootstrap.ts @@ -1,11 +1,11 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { AppScreenService } from '@cloudbeaver/core-app'; +import { AppScreenService, SkipNavService } from '@cloudbeaver/core-app'; import { ActionSnackbar, importLazyComponent } from '@cloudbeaver/core-blocks'; import { LocalStorageSaveService } from '@cloudbeaver/core-browser'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; @@ -17,6 +17,7 @@ import { MENU_APP_STATE } from '@cloudbeaver/plugin-top-app-bar'; import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs'; import { ACTION_APP_HELP } from './actions/ACTION_APP_HELP.js'; +import { SkipNavShortcutsLink } from './SkipNavShortcutsLink.js'; const ShortcutsDialog = importLazyComponent(() => import('./Shortcuts/ShortcutsDialog.js').then(m => m.ShortcutsDialog)); const WelcomeDocs = importLazyComponent(() => import('./WelcomeDocs.js').then(m => m.WelcomeDocs)); @@ -29,6 +30,7 @@ const WelcomeDocs = importLazyComponent(() => import('./WelcomeDocs.js').then(m NotificationService, LocalStorageSaveService, NavigationTabsService, + SkipNavService, ]) export class PluginBootstrap extends Bootstrap { private errorNotification: INotification | null; @@ -40,6 +42,7 @@ export class PluginBootstrap extends Bootstrap { private readonly notificationService: NotificationService, private readonly localStorageSaveService: LocalStorageSaveService, private readonly navigationTabsService: NavigationTabsService, + private readonly skipNavService: SkipNavService, ) { super(); this.errorNotification = null; @@ -51,6 +54,7 @@ export class PluginBootstrap extends Bootstrap { this.navigationTabsService.welcomeContainer.add(WelcomeDocs, undefined); this.addTopAppMenuItems(); this.addMultiTabSupportNotification(); + this.skipNavService.extraLinks.add(SkipNavShortcutsLink); } private addMultiTabSupportNotification() { diff --git a/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx b/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx new file mode 100644 index 00000000000..7ff6c57f4f6 --- /dev/null +++ b/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx @@ -0,0 +1,27 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { importLazyComponent, useTranslate } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { CommonDialogService } from '@cloudbeaver/core-dialogs'; + +const ShortcutsDialog = importLazyComponent(() => import('./Shortcuts/ShortcutsDialog.js').then(m => m.ShortcutsDialog)); + +export function SkipNavShortcutsLink(): React.ReactElement { + const translate = useTranslate(); + const commonDialogService = useService(CommonDialogService); + + function handleClick() { + commonDialogService.open(ShortcutsDialog, undefined); + } + + return ( + + ); +} From e661a60c4aba681bb38f5d0bff6b085d9490616a Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Fri, 27 Mar 2026 18:07:43 +0100 Subject: [PATCH 2/4] dbeaver/pro#7251 refactor: replace direct import with lazy loading for SkipNavLinks and SkipNavShortcutsLink --- webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts | 4 +++- webapp/packages/plugin-help/src/PluginBootstrap.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts b/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts index 9eb16f4bc64..945786abe46 100644 --- a/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts +++ b/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts @@ -8,7 +8,9 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { AppScreenService } from './AppScreenService.js'; -import { SkipNavLinks } from './SkipNavLinks.js'; +import { importLazyComponent } from '@cloudbeaver/core-blocks'; + +const SkipNavLinks = importLazyComponent(() => import('./SkipNavLinks.js').then(m => m.SkipNavLinks)); @injectable(() => [AppScreenService]) export class SkipNavBootstrap extends Bootstrap { diff --git a/webapp/packages/plugin-help/src/PluginBootstrap.ts b/webapp/packages/plugin-help/src/PluginBootstrap.ts index 88ead093d83..fe8a71ef9e2 100644 --- a/webapp/packages/plugin-help/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-help/src/PluginBootstrap.ts @@ -17,8 +17,8 @@ import { MENU_APP_STATE } from '@cloudbeaver/plugin-top-app-bar'; import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs'; import { ACTION_APP_HELP } from './actions/ACTION_APP_HELP.js'; -import { SkipNavShortcutsLink } from './SkipNavShortcutsLink.js'; +const SkipNavShortcutsLink = importLazyComponent(() => import('./SkipNavShortcutsLink.js').then(m => m.SkipNavShortcutsLink)); const ShortcutsDialog = importLazyComponent(() => import('./Shortcuts/ShortcutsDialog.js').then(m => m.ShortcutsDialog)); const WelcomeDocs = importLazyComponent(() => import('./WelcomeDocs.js').then(m => m.WelcomeDocs)); From 615309a01a37352f988d52a15e2fcd585008ebe0 Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Fri, 3 Apr 2026 11:02:48 +0200 Subject: [PATCH 3/4] dbeaver/pro#7251 refactor: use css modules for skip nav styles --- ...ipNavLinks.css => SkipNavLinks.module.css} | 24 +++++++++---------- .../core-app/src/AppScreen/SkipNavLinks.tsx | 18 +++++++------- webapp/packages/core-app/src/index.ts | 3 +++ webapp/packages/plugin-help/package.json | 1 + .../plugin-help/src/SkipNavShortcutsLink.tsx | 12 ++++++---- webapp/packages/plugin-help/tsconfig.json | 3 +++ webapp/yarn.lock | 1 + 7 files changed, 38 insertions(+), 24 deletions(-) rename webapp/packages/core-app/src/AppScreen/{SkipNavLinks.css => SkipNavLinks.module.css} (67%) diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.css b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.module.css similarity index 67% rename from webapp/packages/core-app/src/AppScreen/SkipNavLinks.css rename to webapp/packages/core-app/src/AppScreen/SkipNavLinks.module.css index e96dbc94c38..8b7f4665a82 100644 --- a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.css +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.module.css @@ -1,5 +1,5 @@ @layer components { - .dbv-kit-skip-nav { + .skipNav { position: absolute; top: 8px; left: 8px; @@ -7,7 +7,7 @@ display: flex; } - .dbv-kit-skip-nav__link { + .skipNavLink { position: absolute; width: 1px; height: 1px; @@ -25,16 +25,16 @@ border: var(--dbv-kit-btn-border-width) var(--dbv-kit-btn-border-style) var(--dbv-kit-btn-border-color); border-radius: var(--dbv-kit-btn-border-radius); box-shadow: var(--dbv-kit-dialog-content-shadow); - } - .dbv-kit-skip-nav__link:focus { - position: static; - width: auto; - height: var(--dbv-kit-btn-height); - overflow: visible; - clip: auto; - clip-path: none; - outline: var(--dbv-kit-control-outline-width) solid var(--dbv-kit-control-outline-color); - outline-offset: var(--dbv-kit-control-outline-offset); + &:focus { + position: static; + width: auto; + height: var(--dbv-kit-btn-height); + overflow: visible; + clip: auto; + clip-path: none; + outline: var(--dbv-kit-control-outline-width) solid var(--dbv-kit-control-outline-color); + outline-offset: var(--dbv-kit-control-outline-offset); + } } } diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx index c3e2e95cbd1..cf65d19bc63 100644 --- a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx @@ -5,31 +5,33 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import { Placeholder, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import { UnstyledButton } from '@dbeaver/ui-kit'; import { PANEL_ID_LEFT_SIDEBAR, PANEL_ID_MAIN_CONTENT } from './AppScreenService.js'; import { SkipNavService } from './SkipNavService.js'; -import './SkipNavLinks.css'; +import { skipNav, skipNavLink } from './SkipNavLinks.module.css'; function focusPanel(panelId: string) { const element = document.querySelector(`[data-panel-id="${panelId}"]`); element?.focus(); } -export function SkipNavLinks(): React.ReactElement { +export const SkipNavLinks = observer(function SkipNavLinks(): React.ReactElement { const translate = useTranslate(); const skipNavService = useService(SkipNavService); return ( - ); -} +}); diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index 480dd488d04..92f1d8106e6 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -16,3 +16,6 @@ export * from './AppLocaleService.js'; // components export * from './BodyLazy.js'; + +//styles for skip nav links +export { skipNavLink as skipNavLinkStyles } from './AppScreen/SkipNavLinks.module.css'; diff --git a/webapp/packages/plugin-help/package.json b/webapp/packages/plugin-help/package.json index 4723b022b65..c2ad980f7ec 100644 --- a/webapp/packages/plugin-help/package.json +++ b/webapp/packages/plugin-help/package.json @@ -41,6 +41,7 @@ "@cloudbeaver/plugin-sql-editor": "workspace:*", "@cloudbeaver/plugin-sql-editor-navigation-tab-script": "workspace:*", "@cloudbeaver/plugin-top-app-bar": "workspace:*", + "@dbeaver/ui-kit": "workspace:^", "mobx": "^6", "mobx-react-lite": "^4", "react": "^19", diff --git a/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx b/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx index 7ff6c57f4f6..ea728a2159a 100644 --- a/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx +++ b/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx @@ -5,13 +5,17 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import { importLazyComponent, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; +import { UnstyledButton } from '@dbeaver/ui-kit'; + +import { skipNavLinkStyles } from '@cloudbeaver/core-app'; const ShortcutsDialog = importLazyComponent(() => import('./Shortcuts/ShortcutsDialog.js').then(m => m.ShortcutsDialog)); -export function SkipNavShortcutsLink(): React.ReactElement { +export const SkipNavShortcutsLink = observer(function SkipNavShortcutsLink(): React.ReactElement { const translate = useTranslate(); const commonDialogService = useService(CommonDialogService); @@ -20,8 +24,8 @@ export function SkipNavShortcutsLink(): React.ReactElement { } return ( - + ); -} +}); diff --git a/webapp/packages/plugin-help/tsconfig.json b/webapp/packages/plugin-help/tsconfig.json index 0dad48367f9..acdfa124e70 100644 --- a/webapp/packages/plugin-help/tsconfig.json +++ b/webapp/packages/plugin-help/tsconfig.json @@ -7,6 +7,9 @@ "composite": true }, "references": [ + { + "path": "../../common-react/@dbeaver/ui-kit" + }, { "path": "../core-app" }, diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 035ded3ebc7..0d5c4b16bf5 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3346,6 +3346,7 @@ __metadata: "@cloudbeaver/plugin-sql-editor-navigation-tab-script": "workspace:*" "@cloudbeaver/plugin-top-app-bar": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" + "@dbeaver/ui-kit": "workspace:^" "@types/react": "npm:^19" "@types/react-dom": "npm:^19" mobx: "npm:^6" From 4913789ebd2a50006dbd0e2a4a23195986d08c36 Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Fri, 3 Apr 2026 11:31:45 +0200 Subject: [PATCH 4/4] dbeaver/pro#7251 refactor: remove SkipNavBootstrap --- .../src/AppScreen/AppScreenBootstrap.ts | 12 +++++++--- .../src/AppScreen/SkipNavBootstrap.ts | 24 ------------------- .../core-app/src/AppScreen/SkipNavLinks.tsx | 8 +++---- .../core-app/src/AppScreen/SkipNavService.ts | 14 ++++++++--- webapp/packages/core-app/src/index.ts | 3 ++- webapp/packages/core-app/src/module.ts | 2 -- .../plugin-help/src/SkipNavShortcutsLink.tsx | 4 ++-- 7 files changed, 28 insertions(+), 39 deletions(-) delete mode 100644 webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts diff --git a/webapp/packages/core-app/src/AppScreen/AppScreenBootstrap.ts b/webapp/packages/core-app/src/AppScreen/AppScreenBootstrap.ts index 716df132e2e..5e54a8ebb80 100644 --- a/webapp/packages/core-app/src/AppScreen/AppScreenBootstrap.ts +++ b/webapp/packages/core-app/src/AppScreen/AppScreenBootstrap.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -10,15 +10,19 @@ import { Executor, type IExecutor } from '@cloudbeaver/core-executor'; import { ScreenService } from '@cloudbeaver/core-routing'; import { AppScreenService } from './AppScreenService.js'; +import { SkipNavService } from './SkipNavService.js'; import { importLazyComponent } from '@cloudbeaver/core-blocks'; const AppScreen = importLazyComponent(() => import('./AppScreen.js').then(m => m.AppScreen)); -@injectable(() => [ScreenService]) +@injectable(() => [ScreenService, SkipNavService]) export class AppScreenBootstrap extends Bootstrap { readonly activation: IExecutor; - constructor(private readonly screenService: ScreenService) { + constructor( + private readonly screenService: ScreenService, + private readonly skipNavService: SkipNavService, + ) { super(); this.activation = new Executor(); } @@ -33,5 +37,7 @@ export class AppScreenBootstrap extends Bootstrap { await this.activation.execute(); }, }); + + this.skipNavService.registerLinks(); } } diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts b/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts deleted file mode 100644 index 945786abe46..00000000000 --- a/webapp/packages/core-app/src/AppScreen/SkipNavBootstrap.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2026 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { Bootstrap, injectable } from '@cloudbeaver/core-di'; - -import { AppScreenService } from './AppScreenService.js'; -import { importLazyComponent } from '@cloudbeaver/core-blocks'; - -const SkipNavLinks = importLazyComponent(() => import('./SkipNavLinks.js').then(m => m.SkipNavLinks)); - -@injectable(() => [AppScreenService]) -export class SkipNavBootstrap extends Bootstrap { - constructor(private readonly appScreenService: AppScreenService) { - super(); - } - - override register(): void { - this.appScreenService.placeholder.add(SkipNavLinks, 0); - } -} diff --git a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx index cf65d19bc63..ec7e8fadc6b 100644 --- a/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx @@ -12,7 +12,7 @@ import { UnstyledButton } from '@dbeaver/ui-kit'; import { PANEL_ID_LEFT_SIDEBAR, PANEL_ID_MAIN_CONTENT } from './AppScreenService.js'; import { SkipNavService } from './SkipNavService.js'; -import { skipNav, skipNavLink } from './SkipNavLinks.module.css'; +import styles from './SkipNavLinks.module.css'; function focusPanel(panelId: string) { const element = document.querySelector(`[data-panel-id="${panelId}"]`); @@ -24,11 +24,11 @@ export const SkipNavLinks = observer(function SkipNavLinks(): React.ReactElement const skipNavService = useService(SkipNavService); return ( -