diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Dialog/Dialog.css b/webapp/common-react/@dbeaver/ui-kit/src/Dialog/Dialog.css
new file mode 100644
index 00000000000..fade052ace1
--- /dev/null
+++ b/webapp/common-react/@dbeaver/ui-kit/src/Dialog/Dialog.css
@@ -0,0 +1,213 @@
+@import './_base.css';
+
+@layer base {
+ .dbv-kit-dialog__backdrop {
+ background-color: transparent;
+ opacity: 0;
+ transition-property: opacity, background-color;
+ transition-timing-function: ease-in-out;
+ transition-duration: 100ms;
+
+ &[data-enter] {
+ opacity: 1;
+ background-color: var(--dbv-kit-dialog-backdrop-background);
+ }
+
+ &:not([data-enter]) {
+ opacity: 0;
+ }
+
+ &[data-animated='false'] {
+ transition: none !important;
+ opacity: 1;
+ background-color: var(--dbv-kit-dialog-backdrop-background);
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ transition: none !important;
+ opacity: 1;
+ background-color: var(--dbv-kit-dialog-backdrop-background);
+ }
+ }
+
+ .dbv-kit-dialog {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ background-color: var(--dbv-kit-dialog-content-background);
+ color: var(--dbv-kit-dialog-content-foreground);
+ border: none;
+ border-radius: var(--dbv-kit-dialog-content-border-radius);
+ box-shadow: var(--dbv-kit-dialog-content-shadow);
+ outline: 0;
+ overflow: hidden;
+ margin: 0;
+ max-width: var(--dbv-kit-dialog-large-width);
+
+ @media (prefers-reduced-motion: no-preference) {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0.95);
+ transition-property: opacity, transform;
+ transition-timing-function: ease-in-out;
+ transition-duration: 150ms;
+
+ &:not([data-enter]) {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0.95);
+ }
+ &[data-enter] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ }
+
+ &[data-animated='false'] {
+ transition: none !important;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ }
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ transition: none !important;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ }
+
+ &[data-size='small'] {
+ min-width: var(--dbv-kit-dialog-small-width);
+ min-height: var(--dbv-kit-dialog-small-height);
+ max-height: max(var(--app-height, 100vh) - 48px, var(--dbv-kit-dialog-small-height));
+ }
+
+ &[data-size='medium'] {
+ min-width: var(--dbv-kit-dialog-medium-width);
+ min-height: var(--dbv-kit-dialog-medium-height);
+ max-height: max(var(--app-height, 100vh) - 48px, var(--dbv-kit-dialog-medium-height));
+ }
+
+ &[data-size='large'] {
+ min-width: var(--dbv-kit-dialog-large-width);
+ min-height: var(--dbv-kit-dialog-large-height);
+ max-height: max(var(--app-height, 100vh) - 48px, var(--dbv-kit-dialog-large-height));
+ }
+
+ &[data-size='free'] {
+ min-width: auto;
+ min-height: auto;
+ max-width: calc(100vw - 48px);
+ max-height: calc(100vh - 48px);
+ }
+
+ /* Slide variant (side panel from right) */
+ &[data-variant='slide'] {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ transform: translateX(100%);
+ width: 100%;
+ max-width: none;
+ height: 100%;
+ max-height: 100%;
+ min-width: 0;
+ min-height: 0;
+ border-radius: 0;
+
+ @media (prefers-reduced-motion: no-preference) {
+ opacity: 1;
+ transition-property: transform;
+ transition-timing-function: var(--dbv-kit-dialog-slide-transition-timing);
+ transition-duration: var(--dbv-kit-dialog-slide-transition-duration);
+
+ &[data-enter] {
+ transform: translateX(0);
+ }
+
+ &:not([data-enter]) {
+ transform: translateX(100%);
+ }
+
+ &[data-animated='false'] {
+ transition: none !important;
+ transform: translateX(0);
+ }
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ transition: none !important;
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+ }
+
+ .dbv-kit-dialog__header {
+ flex-shrink: 0;
+ padding: var(--dbv-kit-dialog-header-padding);
+ border-bottom: var(--dbv-kit-dialog-header-border-bottom);
+ }
+
+ .dbv-kit-dialog__body {
+ flex: 1 1 auto;
+ overflow-y: auto;
+ padding: var(--dbv-kit-dialog-body-padding);
+ }
+
+ .dbv-kit-dialog__footer {
+ flex-shrink: 0;
+ padding: var(--dbv-kit-dialog-footer-padding);
+ border-top: var(--dbv-kit-dialog-footer-border-top);
+ display: flex;
+ gap: var(--dbv-kit-dialog-footer-gap);
+ justify-content: flex-end;
+ }
+
+ .dbv-kit-dialog__disclosure {
+ background-color: var(--dbv-kit-dialog-disclosure-background);
+ color: var(--dbv-kit-dialog-disclosure-foreground);
+ }
+
+ .dbv-kit-dialog__heading {
+ margin: var(--dbv-kit-dialog-heading-margin);
+ font-size: var(--dbv-kit-dialog-heading-font-size);
+ font-weight: var(--dbv-kit-dialog-heading-font-weight);
+ line-height: var(--dbv-kit-dialog-heading-line-height);
+ color: var(--dbv-kit-dialog-heading-color);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .dbv-kit-dialog__description {
+ margin: var(--dbv-kit-dialog-description-margin);
+ font-size: var(--dbv-kit-dialog-description-font-size);
+ color: var(--dbv-kit-dialog-description-color);
+ }
+
+ .dbv-kit-dialog__dismiss {
+ background-color: var(--dbv-kit-dialog-dismiss-background);
+ color: var(--dbv-kit-dialog-dismiss-foreground);
+ padding: 0.5rem 1rem;
+ border-radius: 0.375rem;
+ border: none;
+ cursor: pointer;
+ font-weight: 500;
+ font-size: 0.875rem;
+ transition: background-color 150ms var(--tw-ease-in-out);
+
+ &:hover:not([aria-disabled='true']) {
+ background-color: var(--dbv-kit-dialog-dismiss-hover-background);
+ }
+
+ &[aria-disabled='true'] {
+ opacity: var(--dbv-kit-control-disabled-opacity, 0.5);
+ cursor: not-allowed;
+ }
+ }
+}
diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Dialog/Dialog.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Dialog/Dialog.tsx
new file mode 100644
index 00000000000..b8e4e66ddb3
--- /dev/null
+++ b/webapp/common-react/@dbeaver/ui-kit/src/Dialog/Dialog.tsx
@@ -0,0 +1,92 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2025 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 {
+ Dialog as AriakitDialog,
+ DialogDescription as AriakitDialogDescription,
+ DialogDisclosure as AriakitDialogDisclosure,
+ DialogDismiss as AriakitDialogDismiss,
+ DialogHeading as AriakitDialogHeading,
+ DialogProvider,
+ type DialogDescriptionProps,
+ type DialogDismissProps,
+ type DialogDisclosureProps,
+ type DialogHeadingProps,
+ type DialogProps,
+ type DialogProviderProps,
+ type DialogStore,
+ type DialogStoreProps,
+ type DialogStoreState,
+ useDialogContext,
+ useDialogStore,
+} from '@ariakit/react';
+import clsx from 'clsx';
+import type { ComponentPropsWithoutRef, JSX } from 'react';
+
+import './Dialog.css';
+
+interface ExtendedDialogProps extends DialogProps {
+ animated?: boolean;
+}
+
+function Dialog({ className, backdrop, animated = true, ...props }: ExtendedDialogProps): JSX.Element {
+ const backdropElement = backdrop === true ?
: backdrop;
+
+ return ;
+}
+
+function DialogDisclosure({ className, ...props }: DialogDisclosureProps): JSX.Element {
+ return ;
+}
+
+function DialogHeader({ className, ...props }: ComponentPropsWithoutRef<'header'>): JSX.Element {
+ return ;
+}
+
+function DialogBody({ className, ...props }: ComponentPropsWithoutRef<'div'>): JSX.Element {
+ return ;
+}
+
+function DialogFooter({ className, ...props }: ComponentPropsWithoutRef<'footer'>): JSX.Element {
+ return ;
+}
+
+function DialogHeading({ className, ...props }: DialogHeadingProps): JSX.Element {
+ return ;
+}
+
+function DialogDescription({ className, ...props }: DialogDescriptionProps): JSX.Element {
+ return ;
+}
+
+function DialogDismiss({ className, ...props }: DialogDismissProps): JSX.Element {
+ return ;
+}
+
+export {
+ Dialog,
+ DialogDisclosure,
+ DialogHeader,
+ DialogBody,
+ DialogFooter,
+ DialogHeading,
+ DialogDescription,
+ DialogDismiss,
+ DialogProvider,
+ useDialogStore,
+ useDialogContext,
+ type ExtendedDialogProps as DialogProps,
+ type DialogProviderProps,
+ type DialogDisclosureProps,
+ type DialogHeadingProps,
+ type DialogDescriptionProps,
+ type DialogDismissProps,
+ type DialogStore,
+ type DialogStoreProps,
+ type DialogStoreState,
+};
diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Dialog/_base.css b/webapp/common-react/@dbeaver/ui-kit/src/Dialog/_base.css
new file mode 100644
index 00000000000..657cc5053a7
--- /dev/null
+++ b/webapp/common-react/@dbeaver/ui-kit/src/Dialog/_base.css
@@ -0,0 +1,66 @@
+@layer base {
+ :root {
+ /* Dialog container */
+ --dbv-kit-dialog-content-background: #ffffff;
+ --dbv-kit-dialog-content-foreground: #353535;
+ --dbv-kit-dialog-content-border-radius: 0.25rem;
+ --dbv-kit-dialog-content-shadow: 0 6px 6px -3px #0003, 0 10px 14px 1px #00000024, 0 4px 18px 3px #0000001f;
+
+ /* Dialog layout */
+ --dbv-kit-dialog-header-padding: 1rem 1.5rem;
+ --dbv-kit-dialog-header-border-bottom: none;
+ --dbv-kit-dialog-body-padding: 1.5rem;
+ --dbv-kit-dialog-footer-padding: 1rem 1.5rem;
+ --dbv-kit-dialog-footer-border-top: none;
+ --dbv-kit-dialog-footer-gap: 0.75rem;
+
+ /* Dialog sizes */
+ --dbv-kit-dialog-small-width: 404px;
+ --dbv-kit-dialog-small-height: 262px;
+ --dbv-kit-dialog-medium-width: 576px;
+ --dbv-kit-dialog-medium-height: 374px;
+ --dbv-kit-dialog-large-width: 720px;
+ --dbv-kit-dialog-large-height: 468px;
+ --dbv-kit-dialog-max-width: 720px;
+
+ /* Slide variant (side panel) */
+ --dbv-kit-dialog-slide-transition-duration: 300ms;
+ --dbv-kit-dialog-slide-transition-timing: ease-in-out;
+
+ /* Backdrop */
+ --dbv-kit-dialog-backdrop-background: #0000007a;
+
+ /* Heading */
+ --dbv-kit-dialog-heading-margin: 0;
+ --dbv-kit-dialog-heading-font-size: 1.25rem;
+ --dbv-kit-dialog-heading-font-weight: 400;
+ --dbv-kit-dialog-heading-line-height: 2rem;
+ --dbv-kit-dialog-heading-color: var(--dbv-kit-dialog-content-foreground);
+
+ /* Description */
+ --dbv-kit-dialog-description-margin: 0;
+ --dbv-kit-dialog-description-font-size: 0.875rem;
+ --dbv-kit-dialog-description-color: inherit;
+
+ /* Disclosure */
+ --dbv-kit-dialog-disclosure-background: transparent;
+ --dbv-kit-dialog-disclosure-foreground: inherit;
+
+ /* Dismiss */
+ --dbv-kit-dialog-dismiss-background: #f2f2f2;
+ --dbv-kit-dialog-dismiss-foreground: #353535;
+ --dbv-kit-dialog-dismiss-hover-background: #e5e5e5;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ :root {
+ --dbv-kit-dialog-content-background: hsl(240deg, 10%, 16%);
+ --dbv-kit-dialog-content-foreground: #ffffff;
+ --dbv-kit-dialog-header-border-bottom: none;
+ --dbv-kit-dialog-footer-border-top: none;
+ --dbv-kit-dialog-dismiss-background: hsl(240deg, 10%, 18%);
+ --dbv-kit-dialog-dismiss-foreground: #cbcbcb;
+ --dbv-kit-dialog-dismiss-hover-background: hsl(240deg, 10%, 22%);
+ }
+ }
+}
diff --git a/webapp/common-react/@dbeaver/ui-kit/src/index.ts b/webapp/common-react/@dbeaver/ui-kit/src/index.ts
index 75716f452c2..2ac792e490c 100644
--- a/webapp/common-react/@dbeaver/ui-kit/src/index.ts
+++ b/webapp/common-react/@dbeaver/ui-kit/src/index.ts
@@ -48,3 +48,4 @@ export * from './utils/clsx.js';
export * from './ComponentProvider.js';
export * from './Menu/Menu.js';
export * from './Disclosure/Disclosure.js';
+export * from './Dialog/Dialog.js';
diff --git a/webapp/packages/core-app/src/AppScreen/RightArea.tsx b/webapp/packages/core-app/src/AppScreen/RightArea.tsx
index e70e1f29de4..9d9d3e9194e 100644
--- a/webapp/packages/core-app/src/AppScreen/RightArea.tsx
+++ b/webapp/packages/core-app/src/AppScreen/RightArea.tsx
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ import {
Split,
useS,
useSplitUserState,
+ SlidePanel,
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { OptionsPanelService } from '@cloudbeaver/core-ui';
@@ -46,7 +47,7 @@ export const RightArea = observer(function RightArea({ className }) {
return (
-
+
@@ -60,13 +61,11 @@ export const RightArea = observer(function RightArea({ className }) {
-
-
-
-
-
-
+
+
+
+
);
});
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.module.css b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.module.css
index 19f3e2bccf7..21523926864 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.module.css
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.module.css
@@ -6,23 +6,18 @@
* you may not use this file except in compliance with the License.
*/
.body {
- flex: 1;
box-sizing: content-box;
display: flex;
max-height: 100%;
overflow: auto;
+ padding: 24px;
padding-top: 0px;
padding-right: 0px;
- flex-shrink: 0;
- padding: 24px;
&.noBodyPadding {
padding: 0px;
}
- padding-top: 0px;
- padding-right: 0px;
-
&.noBodyPadding + footer {
padding-top: 24px;
}
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.tsx b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.tsx
index fe15e846230..671fa44eecd 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.tsx
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogBody.tsx
@@ -1,12 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 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 { DialogBody } from '@dbeaver/ui-kit';
import { s } from '../../s.js';
import { useS } from '../../useS.js';
import styles from './CommonDialogBody.module.css';
@@ -22,11 +23,11 @@ export const CommonDialogBody = observer(function CommonDialogBody({ noBo
const computedStyles = useS(styles);
return (
-
+
{children}
{!noOverflow &&
}
-
+
);
});
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.module.css b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.module.css
index db3f95b8cf5..b2e6f9ba60c 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.module.css
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.module.css
@@ -6,8 +6,7 @@
* you may not use this file except in compliance with the License.
*/
.footer {
- flex-shrink: 0;
- padding: 0 24px 24px 24px;
+ padding: 0 24px 24px;
display: flex;
z-index: 0;
box-sizing: border-box;
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx
index 16dadafa9c3..0a0765838ba 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx
@@ -1,12 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 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 { DialogFooter } from '@dbeaver/ui-kit';
import { s } from '../../s.js';
import { useS } from '../../useS.js';
import styles from './CommonDialogFooter.module.css';
@@ -19,5 +20,5 @@ interface Props {
export const CommonDialogFooter = observer(function CommonDialogFooter({ children, className }) {
const computedStyles = useS(styles);
- return ;
+ return {children};
});
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css
index de47bb038aa..4b0ae89e7e9 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css
@@ -9,12 +9,11 @@
position: relative;
display: grid;
grid-template-columns: max-content 1fr;
- flex-shrink: 0;
- padding: 24px;
- padding-right: 20px;
+ padding: 24px !important;
+ padding-right: 20px !important;
&.noPadding {
- padding: 0px;
+ padding: 0px !important;
}
}
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx
index d0257130632..e513e7f3ffd 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
-import { IconButton } from '@dbeaver/ui-kit';
+import { DialogHeader, IconButton } from '@dbeaver/ui-kit';
import { Icon } from '../../Icon.js';
import { IconOrImage } from '../../IconOrImage.js';
import { useTranslate } from '../../localization/useTranslate.js';
@@ -40,7 +40,7 @@ export const CommonDialogHeader = observer(function CommonDialogHeader({
const computedStyles = useS(styles);
return (
-
+
{icon && }
@@ -53,6 +53,6 @@ export const CommonDialogHeader = observer(function CommonDialogHeader({
)}
{subTitle && {typeof subTitle === 'string' ? translate(subTitle) : subTitle}
}
-
+
);
});
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.module.css b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.module.css
index 2a3e392b803..71c7abd8f41 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.module.css
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.module.css
@@ -5,69 +5,47 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-.container {
- box-sizing: border-box;
- display: flex;
- outline: none;
-}
-
.dialog {
composes: theme-background-surface theme-text-on-surface theme-elevation-z10 from global;
- border-radius: 0.25rem;
- display: flex;
- flex-direction: column;
- position: relative;
- overflow: hidden;
- margin: 0;
- border: none;
- height: auto;
- max-height: 100%;
- max-width: 748px;
- padding: 0px;
- outline: none;
- &.small {
+ &.fixedSize[data-size='small'] {
+ width: 404px;
+ height: 262px;
min-width: 404px;
min-height: 262px;
- max-height: max(var(--app-height, 100vh) - 48px, 262px);
+ }
- &.fixedSize {
- width: 404px;
- height: 262px;
- }
- &.fixedWidth {
- width: 404px;
- }
+ &.fixedWidth[data-size='small'] {
+ width: 404px;
+ min-width: 404px;
}
- &.medium {
+
+ &.fixedSize[data-size='medium'] {
+ width: 576px;
+ height: 374px;
min-width: 576px;
min-height: 374px;
- max-height: max(var(--app-height, 100vh) - 48px, 374px);
+ }
- &.fixedSize {
- width: 576px;
- height: 374px;
- }
- &.fixedWidth {
- width: 576px;
- }
+ &.fixedWidth[data-size='medium'] {
+ width: 576px;
+ min-width: 576px;
}
- &.large {
+
+ &.fixedSize[data-size='large'] {
+ width: 720px;
+ height: 468px;
min-width: 720px;
min-height: 468px;
- max-height: max(var(--app-height, 100vh) - 48px, 468px);
+ }
- &.fixedSize {
- width: 720px;
- height: 468px;
- }
- &.fixedWidth {
- width: 720px;
- }
+ &.fixedWidth[data-size='large'] {
+ width: 720px;
+ min-width: 720px;
}
&.freeHeight {
- min-height: unset;
+ min-height: unset !important;
}
}
diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx
index a5251b27f14..5d94bdeb7f1 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx
+++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx
@@ -1,17 +1,16 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 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 { forwardRef, useContext, useEffect } from 'react';
-import { Dialog, useDialogState } from 'reakit';
+import { forwardRef, useContext } from 'react';
+import { Dialog } from '@dbeaver/ui-kit';
import { Loader } from '../../Loader/Loader.js';
import { s } from '../../s.js';
-import { useFocus } from '../../useFocus.js';
import { useS } from '../../useS.js';
import { DialogContext } from '../DialogContext.js';
import styles from './CommonDialogWrapper.module.css';
@@ -24,50 +23,53 @@ export interface CommonDialogWrapperProps {
fixedSize?: boolean;
fixedWidth?: boolean;
freeHeight?: boolean;
- autofocus?: boolean;
className?: string;
children?: React.ReactNode;
+ autoFocusOnHide?: boolean | ((element: HTMLElement | null) => boolean) | undefined;
+ autoFocusOnShow?: boolean | ((element: HTMLElement | null) => boolean) | undefined;
+ initialFocus?: HTMLElement | React.RefObject | null | undefined;
}
export const CommonDialogWrapper = observer(
forwardRef(function CommonDialogWrapper(
- { size = 'medium', fixedSize, fixedWidth, freeHeight, autofocus = true, 'aria-label': ariaLabel, className, children },
+ {
+ size = 'medium',
+ fixedSize,
+ fixedWidth,
+ freeHeight,
+ 'aria-label': ariaLabel,
+ autoFocusOnHide = true,
+ autoFocusOnShow = true,
+ className,
+ initialFocus,
+ children,
+ },
ref,
) {
- const [focusedRef] = useFocus({ autofocus });
const computedStyles = useS(styles);
const context = useContext(DialogContext);
- const dialogState = useDialogState({ visible: true });
- useEffect(() => {
- if (!dialogState.visible && !context.dialog.options?.persistent) {
+ function handleClose() {
+ if (!context.dialog.options?.persistent) {
context.reject();
}
- });
+ }
return (
);
}),
diff --git a/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.module.css b/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.module.css
index bb063318fd1..9f99726b352 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.module.css
+++ b/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.module.css
@@ -9,6 +9,11 @@
height: 100%;
}
+.error {
+ composes: theme-background-surface theme-text-on-surface theme-elevation-z10 from global;
+ border-radius: 0.25rem;
+}
+
.backdrop {
box-sizing: border-box;
background-color: rgba(0, 0, 0, 0.48);
@@ -35,8 +40,3 @@
display: none;
}
}
-
-.error {
- composes: theme-background-surface theme-text-on-surface theme-elevation-z10 from global;
- border-radius: 0.25rem;
-}
diff --git a/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.tsx b/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.tsx
index 5268006709a..6b6c8aa1df3 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.tsx
+++ b/webapp/packages/core-blocks/src/CommonDialog/DialogsPortal.tsx
@@ -6,8 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
-import { useLayoutEffect, useMemo, useRef } from 'react';
-import { DialogBackdrop } from 'reakit';
+import { useMemo } from 'react';
import { useService } from '@cloudbeaver/core-di';
import { CommonDialogService, type DialogInternal } from '@cloudbeaver/core-dialogs';
@@ -23,7 +22,6 @@ import style from './DialogsPortal.module.css';
export const DialogsPortal = observer(function DialogsPortal() {
const styles = useS(style);
const commonDialogService = useService(CommonDialogService);
- const focusedElementRef = useRef(null);
let activeDialog: DialogInternal | undefined;
@@ -43,48 +41,19 @@ export const DialogsPortal = observer(function DialogsPortal() {
commonDialogService.resolveDialog(this.dialog.promise, result);
}
},
- backdropClick(e: React.MouseEvent) {
- if (e.target !== e.currentTarget) {
- return;
- }
-
- e.preventDefault(); // prevent focus loss
- if (!this.dialog?.options?.persistent && e.currentTarget.isEqualNode(e.target as HTMLElement)) {
- this.reject();
- }
- },
}),
{
dialog: activeDialog,
},
- ['reject', 'resolve', 'backdropClick'],
+ ['reject', 'resolve'],
);
- useMemo(() => {
- if (!activeDialog) {
- return;
- }
-
- // capture focused element before dialog open
- if (document.activeElement instanceof HTMLElement) {
- focusedElementRef.current = document.activeElement;
- }
- }, [activeDialog]);
-
- useLayoutEffect(() => {
- if (!activeDialog) {
- return;
- }
-
- return () => {
- // restore focus after dialog close
- focusedElementRef.current?.focus();
- focusedElementRef.current = null;
- };
- }, [activeDialog]);
+ if (!activeDialog) {
+ return null;
+ }
return (
-
+
{commonDialogService.dialogs.map((dialog, i, arr) => (
@@ -92,7 +61,7 @@ export const DialogsPortal = observer(function DialogsPortal() {
))}
-
+
);
});
diff --git a/webapp/packages/core-blocks/src/CommonDialog/RenameDialog.tsx b/webapp/packages/core-blocks/src/CommonDialog/RenameDialog.tsx
index eddf103901f..7071c43f876 100644
--- a/webapp/packages/core-blocks/src/CommonDialog/RenameDialog.tsx
+++ b/webapp/packages/core-blocks/src/CommonDialog/RenameDialog.tsx
@@ -7,7 +7,7 @@
*/
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
-import { useEffect } from 'react';
+import { useEffect, useRef } from 'react';
import type { DialogComponent } from '@cloudbeaver/core-dialogs';
import { throttleAsync } from '@cloudbeaver/core-utils';
@@ -19,7 +19,6 @@ import { Form } from '../FormControls/Form.js';
import { InputField } from '../FormControls/InputField/InputField.js';
import { useTranslate } from '../localization/useTranslate.js';
import { s } from '../s.js';
-import { useFocus } from '../useFocus.js';
import { useObservableRef } from '../useObservableRef.js';
import { useS } from '../useS.js';
import { CommonDialogBody } from './CommonDialog/CommonDialogBody.js';
@@ -57,7 +56,7 @@ export const RenameDialog: DialogComponent = observ
className,
}) {
const translate = useTranslate();
- const [focusedRef] = useFocus({ focusFirstChild: true });
+ const ref = useRef(null);
const styles = useS(style);
const { icon, subTitle, bigIcon, viewBox, name, objectName, create, confirmActionText } = payload;
@@ -103,12 +102,19 @@ export const RenameDialog: DialogComponent = observ
const errorMessage = state.valid ? ' ' : translate(state.message ?? 'ui_rename_taken_or_invalid');
return (
-
+
-
-
+
diff --git a/webapp/packages/core-blocks/src/Slide/SlideBox.tsx b/webapp/packages/core-blocks/src/Slide/SlideBox.tsx
index 3a0ee17bfcb..78b303e9b57 100644
--- a/webapp/packages/core-blocks/src/Slide/SlideBox.tsx
+++ b/webapp/packages/core-blocks/src/Slide/SlideBox.tsx
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
diff --git a/webapp/packages/core-blocks/src/Slide/SlideElement.tsx b/webapp/packages/core-blocks/src/Slide/SlideElement.tsx
index 0cd567e8696..1ed41d61a6b 100644
--- a/webapp/packages/core-blocks/src/Slide/SlideElement.tsx
+++ b/webapp/packages/core-blocks/src/Slide/SlideElement.tsx
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -15,10 +15,15 @@ interface Props {
className?: string;
children?: React.ReactNode;
open?: boolean;
+ inert?: boolean;
}
-export const SlideElement = observer(function SlideElement({ children, className }) {
+export const SlideElement = observer(function SlideElement({ children, className, inert }) {
const styles = useS(style);
- return {children}
;
+ return (
+
+ {children}
+
+ );
});
diff --git a/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx b/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx
index 6719e2dab19..0bffdcee1c9 100644
--- a/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx
+++ b/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx
@@ -1,13 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 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 { Icon } from '../Icon.js';
import { s } from '../s.js';
import { useS } from '../useS.js';
import style from './SlideOverlay.module.css';
@@ -21,11 +20,5 @@ interface Props {
export const SlideOverlay = observer(function SlideOverlay({ className, onClick }) {
const styles = useS(style);
- return (
-
- );
+ return ;
});
diff --git a/webapp/packages/core-blocks/src/Slide/SlidePanel.module.css b/webapp/packages/core-blocks/src/Slide/SlidePanel.module.css
new file mode 100644
index 00000000000..54c95eb2716
--- /dev/null
+++ b/webapp/packages/core-blocks/src/Slide/SlidePanel.module.css
@@ -0,0 +1,25 @@
+.iconBtn {
+ position: absolute;
+ left: 0;
+ top: 50%;
+ z-index: 1;
+ transform: translateY(-50%) translateX(-175%) rotate(90deg);
+ background-color: var(--theme-surface);
+ box-sizing: border-box;
+ width: 48px;
+ height: 48px;
+ padding: 15px;
+ border-radius: 50%;
+ color: var(--theme-on-surface);
+ overflow: hidden;
+ align-items: center;
+
+ &:hover,
+ &:focus {
+ background-color: color-mix(in srgb, var(--theme-on-surface), var(--theme-surface) 90%);
+ }
+}
+
+.loader {
+ height: 100%;
+}
diff --git a/webapp/packages/core-blocks/src/Slide/SlidePanel.tsx b/webapp/packages/core-blocks/src/Slide/SlidePanel.tsx
new file mode 100644
index 00000000000..83dacace898
--- /dev/null
+++ b/webapp/packages/core-blocks/src/Slide/SlidePanel.tsx
@@ -0,0 +1,45 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2025 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 { IconButton } from '@dbeaver/ui-kit';
+import { SlideElement, Icon, Loader, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
+
+interface SlidePanelProps {
+ children?: React.ReactNode;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+import style from './SlidePanel.module.css';
+
+export const SLIDE_PANEL_CLOSE_BUTTON_ID = 'slide-panel-close-button';
+
+export function SlidePanel({ children, isOpen, onClose }: SlidePanelProps): React.ReactElement {
+ const styles = useS(style);
+ const t = useTranslate();
+
+ return (
+
+ {isOpen && (
+
+
+
+ )}
+
+ {children}
+
+
+ );
+}
diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts
index 01abe042c44..63f5becea75 100644
--- a/webapp/packages/core-blocks/src/index.ts
+++ b/webapp/packages/core-blocks/src/index.ts
@@ -92,6 +92,7 @@ export * from './PropertiesTable/IProperty.js';
export * from './Slide/SlideBox.js';
export * from './Slide/SlideElement.js';
export * from './Slide/SlideOverlay.js';
+export * from './Slide/SlidePanel.js';
export * from './Split/SplitControls.js';
export * from './Split/Pane.js';
diff --git a/webapp/packages/core-blocks/src/locales/en.ts b/webapp/packages/core-blocks/src/locales/en.ts
index df9d23e4549..fe98dd2c2c0 100644
--- a/webapp/packages/core-blocks/src/locales/en.ts
+++ b/webapp/packages/core-blocks/src/locales/en.ts
@@ -16,4 +16,5 @@ export default [
['core_blocks_export_image_dialog_title', 'Export as Image'],
['core_blocks_export_image_dialog_format', 'File Format'],
['core_blocks_export_image_dialog_transparent_background', 'Transparent background'],
+ ['core_blocks_dialog_element_close_tooltip', 'Close panel'],
];
diff --git a/webapp/packages/core-blocks/src/locales/fr.ts b/webapp/packages/core-blocks/src/locales/fr.ts
index 1a7a6111a52..84dd464f37e 100644
--- a/webapp/packages/core-blocks/src/locales/fr.ts
+++ b/webapp/packages/core-blocks/src/locales/fr.ts
@@ -15,4 +15,5 @@ export default [
['core_blocks_export_image_dialog_title', "Exporter en tant qu'image"],
['core_blocks_export_image_dialog_format', 'Format de fichier'],
['core_blocks_export_image_dialog_transparent_background', 'Fond transparent'],
+ ['core_blocks_dialog_element_close_tooltip', 'Fermer le panneau'],
];
diff --git a/webapp/packages/core-blocks/src/locales/it.ts b/webapp/packages/core-blocks/src/locales/it.ts
index 7f301950078..3bbca948354 100644
--- a/webapp/packages/core-blocks/src/locales/it.ts
+++ b/webapp/packages/core-blocks/src/locales/it.ts
@@ -10,4 +10,5 @@ export default [
['core_blocks_export_image_dialog_title', 'Esporta come immagine'],
['core_blocks_export_image_dialog_format', 'Formato file'],
['core_blocks_export_image_dialog_transparent_background', 'Sfondo trasparente'],
+ ['core_blocks_dialog_element_close_tooltip', 'Chiudi pannello'],
];
diff --git a/webapp/packages/core-blocks/src/locales/ru.ts b/webapp/packages/core-blocks/src/locales/ru.ts
index c2092da0ebf..e86ad99e8b9 100644
--- a/webapp/packages/core-blocks/src/locales/ru.ts
+++ b/webapp/packages/core-blocks/src/locales/ru.ts
@@ -13,4 +13,5 @@ export default [
['core_blocks_export_image_dialog_title', 'Экспортировать как изображение'],
['core_blocks_export_image_dialog_format', 'Формат файла'],
['core_blocks_export_image_dialog_transparent_background', 'Прозрачный фон'],
+ ['core_blocks_dialog_element_close_tooltip', 'Закрыть панель'],
];
diff --git a/webapp/packages/core-blocks/src/locales/vi.ts b/webapp/packages/core-blocks/src/locales/vi.ts
index 1a4da9bfe5e..aa6a8f995cb 100644
--- a/webapp/packages/core-blocks/src/locales/vi.ts
+++ b/webapp/packages/core-blocks/src/locales/vi.ts
@@ -16,4 +16,5 @@ export default [
['core_blocks_export_image_dialog_title', 'Xuất dưới dạng hình ảnh'],
['core_blocks_export_image_dialog_format', 'Định dạng tệp'],
['core_blocks_export_image_dialog_transparent_background', 'Nền trong suốt'],
+ ['core_blocks_dialog_element_close_tooltip', 'Đóng bảng điều khiển'],
];
diff --git a/webapp/packages/core-blocks/src/locales/zh.ts b/webapp/packages/core-blocks/src/locales/zh.ts
index 06524303baf..20b008fcbb5 100644
--- a/webapp/packages/core-blocks/src/locales/zh.ts
+++ b/webapp/packages/core-blocks/src/locales/zh.ts
@@ -16,4 +16,5 @@ export default [
['core_blocks_export_image_dialog_title', '导出为图片'],
['core_blocks_export_image_dialog_format', '文件格式'],
['core_blocks_export_image_dialog_transparent_background', '透明背景'],
+ ['core_blocks_dialog_element_close_tooltip', '关闭面板'],
];
diff --git a/webapp/packages/plugin-administration/src/Administration/Administration.tsx b/webapp/packages/plugin-administration/src/Administration/Administration.tsx
index 3cbfc3268c3..4c3701e6cb8 100644
--- a/webapp/packages/plugin-administration/src/Administration/Administration.tsx
+++ b/webapp/packages/plugin-administration/src/Administration/Administration.tsx
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
+ * Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@ import {
type IAdministrationItemRoute,
} from '@cloudbeaver/core-administration';
import {
- Loader,
s,
SContext,
SlideBox,
@@ -26,6 +25,7 @@ import {
ToolsPanelStyles,
useAutoLoad,
useS,
+ SlidePanel,
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { OptionsPanelService, TabList, TabListStyles, TabsState, TabStyles } from '@cloudbeaver/core-ui';
@@ -140,19 +140,17 @@ export const Administration = observer>(function
{children}
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx
index 6ee2d1cc3d5..9e8ceb72880 100644
--- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx
+++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx
@@ -150,10 +150,10 @@ export const AuthDialog: DialogComponent = observer(function AuthD
}}
>
= observer(function
});
return (
-
+ {
+ const finalFocus = document.getElementById(SLIDE_PANEL_CLOSE_BUTTON_ID);
+ finalFocus?.focus();
+
+ return false;
+ }}
+ fixedSize
+ >
diff --git a/webapp/packages/plugin-projects/src/FolderDialog.tsx b/webapp/packages/plugin-projects/src/FolderDialog.tsx
index a178327af91..6a770179ef4 100644
--- a/webapp/packages/plugin-projects/src/FolderDialog.tsx
+++ b/webapp/packages/plugin-projects/src/FolderDialog.tsx
@@ -7,7 +7,7 @@
*/
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
-import { useEffect } from 'react';
+import { useEffect, useRef } from 'react';
import {
Button,
@@ -21,7 +21,6 @@ import {
InputField,
s,
Translate,
- useFocus,
useObservableRef,
useResource,
useS,
@@ -78,7 +77,7 @@ export const FolderDialog: DialogComponent({ focusFirstChild: true });
+ const inputRef = useRef(null);
const { icon, folder, bigIcon, viewBox, value, projectId, selectProject, objectName, create, confirmActionText, filterProject } = payload;
let { title } = payload;
@@ -162,12 +161,13 @@ export const FolderDialog: DialogComponent
+
-