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/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/SkipNavLinks.module.css b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.module.css new file mode 100644 index 00000000000..8b7f4665a82 --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.module.css @@ -0,0 +1,40 @@ +@layer components { + .skipNav { + position: absolute; + top: 8px; + left: 8px; + z-index: 10000; + display: flex; + } + + .skipNavLink { + 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); + + &: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..ec7e8fadc6b --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavLinks.tsx @@ -0,0 +1,37 @@ +/* + * 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 { 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 styles from './SkipNavLinks.module.css'; + +function focusPanel(panelId: string) { + const element = document.querySelector(`[data-panel-id="${panelId}"]`); + element?.focus(); +} + +export const SkipNavLinks = observer(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..d162a6d5111 --- /dev/null +++ b/webapp/packages/core-app/src/AppScreen/SkipNavService.ts @@ -0,0 +1,26 @@ +/* + * 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, PlaceholderContainer } from '@cloudbeaver/core-blocks'; +import { injectable } from '@cloudbeaver/core-di'; + +import { AppScreenService } from './AppScreenService.js'; + +const SkipNavLinks = importLazyComponent(() => import('./SkipNavLinks.js').then(m => m.SkipNavLinks)); + +@injectable(() => [AppScreenService]) +export class SkipNavService { + readonly extraLinks: PlaceholderContainer; + + constructor(private readonly appScreenService: AppScreenService) { + this.extraLinks = new PlaceholderContainer(); + } + + registerLinks(): void { + this.appScreenService.placeholder.add(SkipNavLinks, 0); + } +} diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index b44de29398e..f822be9e57b 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,8 +10,13 @@ import './module.js'; // Services export * from './AppScreen/AppScreenService.js'; export * from './AppScreen/AppScreenBootstrap.js'; +export * from './AppScreen/SkipNavService.js'; export * from './AppLocaleService.js'; // components export * from './BodyLazy.js'; + +//styles for skip nav links +import styles from './AppScreen/SkipNavLinks.module.css'; +export { styles as skipNavStyles }; 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..fc98093cbdc 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,17 @@ import { Bootstrap, ModuleRegistry } from '@cloudbeaver/core-di'; import { AppScreenService } from './AppScreen/AppScreenService.js'; import { AppScreenBootstrap } from './AppScreen/AppScreenBootstrap.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(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/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/PluginBootstrap.ts b/webapp/packages/plugin-help/src/PluginBootstrap.ts index d624a4637cd..fe8a71ef9e2 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'; @@ -18,6 +18,7 @@ import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs'; import { ACTION_APP_HELP } from './actions/ACTION_APP_HELP.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)); @@ -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..6d807aeacf6 --- /dev/null +++ b/webapp/packages/plugin-help/src/SkipNavShortcutsLink.tsx @@ -0,0 +1,31 @@ +/* + * 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 { 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 { skipNavStyles } from '@cloudbeaver/core-app'; + +const ShortcutsDialog = importLazyComponent(() => import('./Shortcuts/ShortcutsDialog.js').then(m => m.ShortcutsDialog)); + +export const SkipNavShortcutsLink = observer(function SkipNavShortcutsLink(): React.ReactElement { + const translate = useTranslate(); + const commonDialogService = useService(CommonDialogService); + + function handleClick() { + commonDialogService.open(ShortcutsDialog, undefined); + } + + return ( + + {translate('shortcuts_title')} + + ); +}); 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 d287b3be99e..170d2927a1c 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3338,6 +3338,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"