diff --git a/webapp/packages/core-blocks/src/Button.tsx b/webapp/packages/core-blocks/src/Button.tsx index 21072af5fb..6bcfab94dd 100644 --- a/webapp/packages/core-blocks/src/Button.tsx +++ b/webapp/packages/core-blocks/src/Button.tsx @@ -13,6 +13,7 @@ import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { IconOrImage } from './IconOrImage'; import { Loader } from './Loader/Loader'; +import { useObjectRef } from './useObjectRef'; import { useObservableRef } from './useObservableRef'; import { useStyles } from './useStyles'; @@ -123,16 +124,12 @@ export const Button = observer(function Button({ className, ...rest }) { + const handlersRef = useObjectRef({ onClick }); const state = useObservableRef( () => ({ loading: false, - }), - { - loading: observable.ref, - }, - { click(e: React.MouseEvent) { - const returnValue = onClick?.(e); + const returnValue = handlersRef.onClick?.(e); if (returnValue instanceof Promise) { if (loader) { @@ -143,7 +140,11 @@ export const Button = observer(function Button({ } } }, + }), + { + loading: observable.ref, }, + false, ['click'], ); diff --git a/webapp/packages/core-blocks/src/Fill.m.css b/webapp/packages/core-blocks/src/Fill.m.css new file mode 100644 index 0000000000..d023b7abd3 --- /dev/null +++ b/webapp/packages/core-blocks/src/Fill.m.css @@ -0,0 +1,3 @@ +.fill { + flex: 1; +} diff --git a/webapp/packages/core-blocks/src/Fill.tsx b/webapp/packages/core-blocks/src/Fill.tsx new file mode 100644 index 0000000000..ff6837a52b --- /dev/null +++ b/webapp/packages/core-blocks/src/Fill.tsx @@ -0,0 +1,19 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 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 style from './Fill.m.css'; +import { s } from './s'; +import { useS } from './useS'; + +interface Props { + className?: string; +} + +export const Fill: React.FC = function Fill({ className }) { + const styles = useS(style); + return
; +}; diff --git a/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.m.css b/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.m.css new file mode 100644 index 0000000000..6069bbea3e --- /dev/null +++ b/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.m.css @@ -0,0 +1,5 @@ +.folderExplorerPath { + composes: theme-typography--caption from global; + display: flex; + flex-wrap: wrap; +} diff --git a/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.tsx b/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.tsx index 7a11ad662f..50e23740a6 100644 --- a/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.tsx +++ b/webapp/packages/core-blocks/src/FolderExplorer/FolderExplorerPath.tsx @@ -7,10 +7,11 @@ */ import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; -import styled from 'reshadow'; +import { s } from '../s'; +import { useS } from '../useS'; import { FolderExplorerContext } from './FolderExplorerContext'; -import { folderExplorerStyles } from './folderExplorerStyles'; +import style from './FolderExplorerPath.m.css'; import { FolderName } from './FolderName'; interface Props { @@ -20,6 +21,7 @@ interface Props { } export const FolderExplorerPath = observer(function FolderExplorerPath({ getName, canSkip, className }) { + const styles = useS(style); const context = useContext(FolderExplorerContext); if (!context) { @@ -60,5 +62,5 @@ export const FolderExplorerPath = observer(function FolderExplorerPath({ } } - return styled(folderExplorerStyles)({pathElements}); + return
{pathElements}
; }); diff --git a/webapp/packages/core-blocks/src/FolderExplorer/FolderName.m.css b/webapp/packages/core-blocks/src/FolderExplorer/FolderName.m.css new file mode 100644 index 0000000000..e443aa8525 --- /dev/null +++ b/webapp/packages/core-blocks/src/FolderExplorer/FolderName.m.css @@ -0,0 +1,30 @@ +.path { + composes: theme-typography--caption from global; + display: flex; + flex-wrap: wrap; +} + +.pathElement { + display: flex; + align-items: center; + + &:first-child { + & .pathElementArrow { + display: none; + } + } +} + +.pathElementArrow { + width: 16px; + height: 16px; + transform: rotate(-90deg); + opacity: 0.5; +} + +.pathElementName { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 4px; +} diff --git a/webapp/packages/core-blocks/src/FolderExplorer/FolderName.tsx b/webapp/packages/core-blocks/src/FolderExplorer/FolderName.tsx index 6edbd1cfc5..8fb4f05047 100644 --- a/webapp/packages/core-blocks/src/FolderExplorer/FolderName.tsx +++ b/webapp/packages/core-blocks/src/FolderExplorer/FolderName.tsx @@ -7,12 +7,13 @@ */ import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; -import styled from 'reshadow'; import { Icon } from '../Icon'; import { Link } from '../Link'; +import { s } from '../s'; +import { useS } from '../useS'; import { FolderExplorerContext } from './FolderExplorerContext'; -import { folderExplorerStyles } from './folderExplorerStyles'; +import style from './FolderName.m.css'; interface BaseProps { folder?: string; @@ -36,6 +37,7 @@ interface ShortProps extends BaseProps { } export const FolderName = observer(function FolderName({ folder, path, title, short, last, getName }) { + const styles = useS(style); const context = useContext(FolderExplorerContext); if (!context) { @@ -57,14 +59,12 @@ export const FolderName = observer(function FolderName path = path.slice(0, path.length - 1); } - return styled(folderExplorerStyles)( - - + return ( +
+
- - - {last ? name : context.open(path, folder!)}>{name}} - - , +
+
{last ? name : context.open(path, folder!)}>{name}}
+
); }); diff --git a/webapp/packages/core-blocks/src/FolderExplorer/folderExplorerStyles.ts b/webapp/packages/core-blocks/src/FolderExplorer/folderExplorerStyles.ts deleted file mode 100644 index e6dd4adf51..0000000000 --- a/webapp/packages/core-blocks/src/FolderExplorer/folderExplorerStyles.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 { css } from 'reshadow'; - -export const folderExplorerStyles = css` - folder-explorer-path { - composes: theme-typography--caption from global; - display: flex; - flex-wrap: wrap; - } - - folder-explorer-path-element { - display: flex; - align-items: center; - } - - folder-explorer-path-element-arrow { - width: 16px; - height: 16px; - transform: rotate(-90deg); - opacity: 0.5; - } - - folder-explorer-path-element-name { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - padding: 0 4px; - } - - folder-explorer-path-element:first-child folder-explorer-path-element-arrow { - display: none; - } -`; diff --git a/webapp/packages/core-blocks/src/FolderExplorer/useFolderExplorer.ts b/webapp/packages/core-blocks/src/FolderExplorer/useFolderExplorer.ts index f866859472..d8765c1221 100644 --- a/webapp/packages/core-blocks/src/FolderExplorer/useFolderExplorer.ts +++ b/webapp/packages/core-blocks/src/FolderExplorer/useFolderExplorer.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ import { action, observable } from 'mobx'; -import { useContext, useMemo } from 'react'; +import { useContext, useEffect } from 'react'; import { useObservableRef } from '../useObservableRef'; import { useUserData } from '../useUserData'; @@ -26,15 +26,17 @@ export function useFolderExplorer(root: string, options: IFolderExplorerOptions data => typeof data === 'object' && typeof data.folder === 'string' && Array.isArray(data.path) && Array.isArray(data.fullPath), ); - useMemo( + const saveState = options.saveState; + + useEffect( action(() => { - if (!options.saveState) { + if (!saveState) { userState.folder = root; userState.fullPath = [root]; userState.path = []; } }), - [userState], + [userState, saveState], ); const data = useObservableRef( diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx index 9e8a1cc245..aec3a63a35 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx @@ -16,6 +16,7 @@ import { CheckboxMarkup, CheckboxMod } from './CheckboxMarkup'; import { CheckboxOnChangeEvent, useCheckboxState } from './useCheckboxState'; export interface CheckboxBaseProps { + caption?: string; mod?: CheckboxMod[]; ripple?: boolean; indeterminate?: boolean; diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx index 4acf4fd0ff..3c6b8d0ed9 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx @@ -47,6 +47,9 @@ const checkboxStyles = css` composes: theme-typography--body2 from global; cursor: pointer; } + checkbox-caption { + composes: theme-text-text-hint-on-light theme-typography--caption from global; + } `; const checkboxMod: Record = { @@ -92,6 +95,7 @@ const checkboxState = { interface ICheckboxMarkupProps extends Omit, 'style'> { label?: string; + caption?: string; indeterminate?: boolean; ripple?: boolean; mod?: CheckboxMod[]; @@ -110,6 +114,7 @@ export const CheckboxMarkup: React.FC = function CheckboxM ripple = true, style, readOnly, + caption, ...rest }) { const styles = useS(CheckboxMarkupStyles); @@ -144,6 +149,7 @@ export const CheckboxMarkup: React.FC = function CheckboxM {label && (id || rest.name) && ( {label} + {caption && {caption}} )} , diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx index 1b810a38d4..db90e7c33a 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx @@ -5,7 +5,7 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { getLayoutProps } from '../../Containers/filterLayoutFakeProps'; +import { filterLayoutFakeProps, getLayoutProps } from '../../Containers/filterLayoutFakeProps'; import elementsSizeStyles from '../../Containers/shared/ElementsSize.m.css'; import { s } from '../../s'; import { useS } from '../../useS'; @@ -20,18 +20,21 @@ export const FieldCheckbox: CheckboxType = function FieldCheckbox({ ...rest }: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps)) { const layoutProps = getLayoutProps(rest); + const checkboxProps = filterLayoutFakeProps(rest); const styles = useS(elementsSizeStyles, formControlStyles, fieldCheckboxStyles); - if (rest.autoHide && !isControlPresented(rest.name, rest.state)) { + if (checkboxProps.autoHide && !isControlPresented(checkboxProps.name, checkboxProps.state)) { return null; } return (
- - + + {children && ( + + )}
); }; diff --git a/webapp/packages/core-blocks/src/FormControls/InputField.tsx b/webapp/packages/core-blocks/src/FormControls/InputField.tsx index a456043934..77dfb6ed06 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputField.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputField.tsx @@ -12,7 +12,7 @@ import styled, { use } from 'reshadow'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { isNotNullDefined } from '@cloudbeaver/core-utils'; -import { getLayoutProps } from '../Containers/filterLayoutFakeProps'; +import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css'; import { Icon } from '../Icon'; @@ -100,6 +100,7 @@ export const InputField: InputFieldType = observer( const [passwordRevealed, setPasswordRevealed] = useState(false); const translate = useTranslate(); const layoutProps = getLayoutProps(rest); + rest = filterLayoutFakeProps(rest); const propStyles = useStyles(style); const styles = useS(inputFieldStyle, formControlStyles, elementsSizeStyles); const context = useContext(FormContext); diff --git a/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx b/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx index f02e852455..1fc5202f6c 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx @@ -13,7 +13,7 @@ import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { blobToData, bytesToSize } from '@cloudbeaver/core-utils'; import { Button } from '../Button'; -import { getLayoutProps } from '../Containers/filterLayoutFakeProps'; +import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css'; import { IconButton } from '../IconButton'; @@ -96,6 +96,7 @@ export const InputFileTextContent: InputFileTextContentType = observer(function const [error, setError] = useState(null); const layoutProps = getLayoutProps(rest); + rest = filterLayoutFakeProps(rest); const styles = useStyles(INPUT_FILE_FIELD_STYLES, baseFormControlStyles, style, error ? baseInvalidFormControlStyles : baseValidFormControlStyles); const sizeStyles = useS(elementsSizeStyles); diff --git a/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx b/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx index 085ba2043b..dc9bd10f60 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx @@ -12,7 +12,7 @@ import styled, { css, use } from 'reshadow'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { Button } from '../Button'; -import { getLayoutProps } from '../Containers/filterLayoutFakeProps'; +import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css'; import { useTranslate } from '../localization/useTranslate'; @@ -110,6 +110,7 @@ export const InputFiles: InputFilesType = observer( refInherit: React.Ref, ) { const layoutProps = getLayoutProps(rest); + rest = filterLayoutFakeProps(rest); const ref = useRefInherit(refInherit); const [innerState, setInnerState] = useState(null); const translate = useTranslate(); diff --git a/webapp/packages/core-blocks/src/FormControls/Textarea.tsx b/webapp/packages/core-blocks/src/FormControls/Textarea.tsx index c83225d0c1..bbfaa111a8 100644 --- a/webapp/packages/core-blocks/src/FormControls/Textarea.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Textarea.tsx @@ -11,7 +11,7 @@ import styled, { css, use } from 'reshadow'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; -import { getLayoutProps } from '../Containers/filterLayoutFakeProps'; +import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css'; import { s } from '../s'; @@ -91,6 +91,7 @@ export const Textarea: TextareaType = observer(function Textarea({ ...rest }: ControlledProps | ObjectProps) { const layoutProps = getLayoutProps(rest); + rest = filterLayoutFakeProps(rest); const sizeStyles = useS(elementsSizeStyles); const context = useContext(FormContext); diff --git a/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts b/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts deleted file mode 100644 index 46317c1020..0000000000 --- a/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 { css } from 'reshadow'; - -export const OVERLAY_BASE_STYLES = css` - overlay { - composes: theme-text-on-primary from global; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.4); - - &:not([|active]) { - display: none; - } - - &[|fill] { - background-color: var(--theme-background-surface); - } - } - - overlay { - margin: auto; - } - - overlay, - box { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - overflow: auto; - } - - box { - composes: theme-background-surface theme-text-on-surface theme-elevation-z6 from global; - border-radius: 0.25rem; - align-items: normal; - max-width: calc(100% - 48px); - max-height: calc(100% - 48px); - } - - overlay[|fill] box { - box-shadow: none; - } - - actions { - gap: 16px; - display: flex; - } - - header, - message, - actions { - flex-shrink: 0; - padding: 24px; - } - - message { - padding-right: 0px; - overflow: auto; - flex: 1; - - &:not(:first-child) { - padding-top: 0px; - } - } - - message-box { - padding-right: 24px; - } - - header { - position: relative; - display: grid; - grid-template-columns: max-content 1fr; - flex: 0 0 auto; - box-sizing: border-box; - } - - header-title, - sub-title { - max-width: 400px; - } - - header-title { - display: flex; - align-items: center; - justify-content: space-between; - position: relative; - min-height: 24px; - overflow: auto; - } - - icon-container { - display: flex; - align-items: center; - justify-content: center; - } - - IconOrImage { - width: 24px; - height: 24px; - margin-right: 16px; - } - - h3 { - margin: 0; - overflow: hidden; - text-overflow: ellipsis; - } - - sub-title { - composes: theme-typography--caption from global; - grid-column: 2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -`; diff --git a/webapp/packages/core-blocks/src/Overlay/Overlay.m.css b/webapp/packages/core-blocks/src/Overlay/Overlay.m.css new file mode 100644 index 0000000000..e3afd4eac0 --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/Overlay.m.css @@ -0,0 +1,39 @@ +.overlay { + composes: theme-text-on-primary from global; + display: none; + flex-direction: column; + justify-content: center; + align-items: center; + overflow: auto; + position: absolute; + margin: auto; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); + box-shadow: none; + + &.active { + display: flex; + } + + &.fill { + background-color: var(--theme-background-surface); + & .box { + box-shadow: none; + } + } +} + +.box { + composes: theme-background-surface theme-text-on-surface theme-elevation-z6 from global; + display: flex; + flex-direction: column; + justify-content: center; + overflow: auto; + border-radius: 0.25rem; + align-items: normal; + max-width: calc(100% - 48px); + max-height: calc(100% - 48px); +} diff --git a/webapp/packages/core-blocks/src/Overlay/Overlay.tsx b/webapp/packages/core-blocks/src/Overlay/Overlay.tsx index 95394888ae..8987b68190 100644 --- a/webapp/packages/core-blocks/src/Overlay/Overlay.tsx +++ b/webapp/packages/core-blocks/src/Overlay/Overlay.tsx @@ -6,9 +6,10 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled, { use } from 'reshadow'; -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './Overlay.m.css'; interface Props { active?: boolean; @@ -17,13 +18,11 @@ interface Props { } export const Overlay = observer>(function Overlay({ active, fill, className, children }) { - if (!active) { - return null; - } + const styles = useS(style); - return styled(OVERLAY_BASE_STYLES)( - - {children} - , + return ( +
+
{children}
+
); }); diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayActions.m.css b/webapp/packages/core-blocks/src/Overlay/OverlayActions.m.css new file mode 100644 index 0000000000..e1377c42ef --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/OverlayActions.m.css @@ -0,0 +1,6 @@ +.actions { + gap: 16px; + display: flex; + flex-shrink: 0; + padding: 24px; +} diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayActions.tsx b/webapp/packages/core-blocks/src/Overlay/OverlayActions.tsx index 92c27b3084..1ab55b2215 100644 --- a/webapp/packages/core-blocks/src/Overlay/OverlayActions.tsx +++ b/webapp/packages/core-blocks/src/Overlay/OverlayActions.tsx @@ -6,14 +6,17 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled from 'reshadow'; -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './OverlayActions.m.css'; interface Props { className?: string; } export const OverlayActions = observer>(function OverlayActions({ className, children }) { - return styled(OVERLAY_BASE_STYLES)({children}); + const styles = useS(style); + + return
{children}
; }); diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeader.m.css b/webapp/packages/core-blocks/src/Overlay/OverlayHeader.m.css new file mode 100644 index 0000000000..84c91dec09 --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeader.m.css @@ -0,0 +1,9 @@ +.header { + flex-shrink: 0; + padding: 24px; + position: relative; + display: grid; + grid-template-columns: max-content 1fr; + flex: 0 0 auto; + box-sizing: border-box; +} diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeader.tsx b/webapp/packages/core-blocks/src/Overlay/OverlayHeader.tsx index b9c7ef92a6..85cc794223 100644 --- a/webapp/packages/core-blocks/src/Overlay/OverlayHeader.tsx +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeader.tsx @@ -5,14 +5,16 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled from 'reshadow'; - -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './OverlayHeader.m.css'; interface Props { className?: string; } export const OverlayHeader: React.FC> = function OverlayHeader({ className, children }) { - return styled(OVERLAY_BASE_STYLES)(
{children}
); + const styles = useS(style); + + return
{children}
; }; diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.m.css b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.m.css new file mode 100644 index 0000000000..eae4c70646 --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.m.css @@ -0,0 +1,11 @@ +.iconContainer { + display: flex; + align-items: center; + justify-content: center; +} + +.iconOrImage { + width: 24px; + height: 24px; + margin-right: 16px; +} diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.tsx b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.tsx index 45df0b3c55..0762bf0903 100644 --- a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.tsx +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderIcon.tsx @@ -5,10 +5,10 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled from 'reshadow'; - import { IconOrImage } from '../IconOrImage'; -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './OverlayHeaderIcon.m.css'; interface Props { icon?: string; @@ -17,14 +17,16 @@ interface Props { } export const OverlayHeaderIcon: React.FC> = function OverlayHeaderIcon({ icon, viewBox, className, children }) { + const styles = useS(style); + if (!icon && !children) { return null; } - return styled(OVERLAY_BASE_STYLES)( - - {icon && } + return ( +
+ {icon && } {children} - , +
); }; diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.m.css b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.m.css new file mode 100644 index 0000000000..2aeef9deda --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.m.css @@ -0,0 +1,8 @@ +.subTitle { + composes: theme-typography--caption from global; + grid-column: 2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 400px; +} diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.tsx b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.tsx index 75de360886..fadf54701a 100644 --- a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.tsx +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderSubTitle.tsx @@ -5,14 +5,16 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled from 'reshadow'; - -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './OverlayHeaderSubTitle.m.css'; interface Props { className?: string; } export const OverlayHeaderSubTitle: React.FC> = function OverlayHeaderSubTitle({ className, children }) { - return styled(OVERLAY_BASE_STYLES)({children}); + const styles = useS(style); + + return
{children}
; }; diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.m.css b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.m.css new file mode 100644 index 0000000000..5ed06b9043 --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.m.css @@ -0,0 +1,17 @@ +.headerTitle { + max-width: 400px; + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + min-height: 24px; + overflow: auto; + margin: 0; + text-overflow: ellipsis; +} + +.headerTitleContent { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.tsx b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.tsx index 578d06a9d4..626823a88e 100644 --- a/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.tsx +++ b/webapp/packages/core-blocks/src/Overlay/OverlayHeaderTitle.tsx @@ -5,18 +5,20 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled from 'reshadow'; - -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './OverlayHeaderTitle.m.css'; interface Props { className?: string; } export const OverlayHeaderTitle: React.FC> = function OverlayHeaderTitle({ className, children }) { - return styled(OVERLAY_BASE_STYLES)( - -

{children}

-
, + const styles = useS(style); + + return ( +
+

{children}

+
); }; diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayMessage.m.css b/webapp/packages/core-blocks/src/Overlay/OverlayMessage.m.css new file mode 100644 index 0000000000..9524b2cf11 --- /dev/null +++ b/webapp/packages/core-blocks/src/Overlay/OverlayMessage.m.css @@ -0,0 +1,14 @@ +.message { + flex-shrink: 0; + padding: 24px 0px 24px 24px; + overflow: auto; + flex: 1; + + &:not(:first-child) { + padding-top: 0px; + } +} + +.messageBox { + padding-right: 24px; +} diff --git a/webapp/packages/core-blocks/src/Overlay/OverlayMessage.tsx b/webapp/packages/core-blocks/src/Overlay/OverlayMessage.tsx index 62daa21e6c..e587d5dac1 100644 --- a/webapp/packages/core-blocks/src/Overlay/OverlayMessage.tsx +++ b/webapp/packages/core-blocks/src/Overlay/OverlayMessage.tsx @@ -6,18 +6,21 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled from 'reshadow'; -import { OVERLAY_BASE_STYLES } from './OVERLAY_BASE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './OverlayMessage.m.css'; interface Props { className?: string; } export const OverlayMessage = observer>(function OverlayMessage({ className, children }) { - return styled(OVERLAY_BASE_STYLES)( - - {children} - , + const styles = useS(style); + + return ( +
+
{children}
+
); }); diff --git a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx index a1e45cc736..dc2c9aff69 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx +++ b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx @@ -145,6 +145,8 @@ export const PropertyItem = observer(function PropertyItem({ property, va }, [property]); const focus = menuOpen; + const keyPlaceholder = String(property.keyPlaceholder); + const valuePlaceholder = String(property.valuePlaceholder); return styled(styles)( @@ -153,7 +155,7 @@ export const PropertyItem = observer(function PropertyItem({ property, va ref={keyInputRef} type="text" name={property.id} - placeholder={property.keyPlaceholder} + placeholder={keyPlaceholder} readOnly={!isDeletable} autoComplete="none" onChange={handleKeyChange} @@ -161,11 +163,11 @@ export const PropertyItem = observer(function PropertyItem({ property, va {property.displayName || property.key} - + - reaction( - () => ({ - canLoad: result.canLoad, - loadKey: propertiesRef.key, - }), - ({ canLoad, loadKey }) => { - refObj.use(loadKey); - if (canLoad && !result.isError()) { - result.load(); - } - }, - { - fireImmediately: true, - delay: 100, - }, - ), - [], - ); + const canLoad = useDeferredValue(getComputed(() => result.canLoad)); + const loadKey = useDeferredValue(getComputed(() => propertiesRef.key)); + + useEffect(() => { + refObj.use(loadKey); + if (canLoad && !result.isError()) { + result.load(); + } + }, [canLoad, loadKey]); if (actions?.forceSuspense) { result.data; diff --git a/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts b/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts deleted file mode 100644 index dbb24634d9..0000000000 --- a/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 { css } from 'reshadow'; - -export const BASE_TABLE_STYLES = css` - table { - composes: theme-typography--body2 from global; - min-width: 100%; - text-align: left; - border-collapse: collapse; - border-color: var(--theme-background); - - & thead { - &[|fixed] { - background: var(--theme-surface); - position: sticky; - top: 0; - z-index: 1; - - & > tr { - border-top: none !important; - border-bottom: none !important; - - & > th { - box-shadow: inset 0 -1px 0 var(--theme-background); - } - } - } - } - - &[|size='big'] { - & > thead > tr > th { - height: 54px; - - &[|min] { - width: 46px; - } - } - - & > tbody > tr > td { - height: 46px; - } - } - - &[|expanded] { - & > tbody > tr:not([|expanded]) { - opacity: 0.85; - } - } - } - - tr { - outline: none; - border-bottom: 1px solid; - border-color: var(--theme-background) !important; - } - - tbody > tr { - &:last-child { - border-bottom: none; - } - - &:focus { - border-color: var(--theme-background); - } - - &:not([|noHover]):hover, - &:not([|noHover])[|selected], - &:not([|noHover])[|expanded] { - background-color: var(--theme-sub-secondary); - } - - &[|disabled] { - opacity: 0.85; - } - } - - th { - box-sizing: border-box; - white-space: nowrap; - padding: 16px; - height: 36px; - padding-top: unset; - padding-bottom: unset; - border-color: var(--theme-background); - text-transform: uppercase; - text-align: left; - text-decoration: none !important; - - &[|min] { - width: 28px; - } - - &[|centerContent] > th-flex { - align-items: center; - justify-content: center; - } - - & > th-flex { - display: flex; - } - - &:last-child { - border-right: none; - } - } - - td { - position: relative; - box-sizing: border-box; - height: 28px; - padding: 0 16px; - transition: padding ease-in-out 0.24s; - - &[|ellipsis] { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - &[|centerContent] > td-flex { - align-items: center; - justify-content: center; - } - - & > td-flex { - display: flex; - } - - &[|expandArea] { - padding: 0; - } - - & > input[type='checkbox'] { - display: block; - height: 16px; - width: 16px; - } - } - - table-item-expand-box { - display: flex; - align-items: center; - cursor: pointer; - width: 16px; - height: 100%; - padding: 0; - - & > Icon { - width: 16px; - height: 16px; - padding: 0; - - &[use|expanded] { - transform: rotate(180deg); - } - } - } -`; diff --git a/webapp/packages/core-blocks/src/Table/Table.m.css b/webapp/packages/core-blocks/src/Table/Table.m.css new file mode 100644 index 0000000000..9223a94f2c --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/Table.m.css @@ -0,0 +1,21 @@ +.table { + composes: theme-typography--body2 from global; + min-width: 100%; + text-align: left; + border-collapse: collapse; + border-color: var(--theme-background); + + &.big { + & > thead > tr > th { + height: 54px; + + &.min { + width: 46px; + } + } + + & > tbody > tr > td { + height: 46px; + } + } +} diff --git a/webapp/packages/core-blocks/src/Table/Table.tsx b/webapp/packages/core-blocks/src/Table/Table.tsx index 357922108c..28ef5d0e1c 100644 --- a/webapp/packages/core-blocks/src/Table/Table.tsx +++ b/webapp/packages/core-blocks/src/Table/Table.tsx @@ -8,11 +8,12 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react-lite'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import styled, { use } from 'reshadow'; +import { s } from '../s'; import { useObjectRef } from '../useObjectRef'; import { useObservableRef } from '../useObservableRef'; -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; +import { useS } from '../useS'; +import style from './Table.m.css'; import { ITableContext, ITableState, TableContext } from './TableContext'; interface Props { @@ -36,6 +37,7 @@ export const Table = observer>(function Table({ onSelect, }) { const props = useObjectRef({ onSelect }); + const styles = useS(style); const [selected] = useState>(() => selectedItems || observable(new Map())); const [expanded] = useState>(() => expandedItems || observable(new Map())); @@ -115,11 +117,9 @@ export const Table = observer>(function Table({ collapse, })); - return styled(BASE_TABLE_STYLES)( + return ( - - {children} -
-
, + {children}
+ ); }); diff --git a/webapp/packages/core-blocks/src/Table/TableBody.tsx b/webapp/packages/core-blocks/src/Table/TableBody.tsx index 8e16cd1701..a8188335bf 100644 --- a/webapp/packages/core-blocks/src/Table/TableBody.tsx +++ b/webapp/packages/core-blocks/src/Table/TableBody.tsx @@ -5,14 +5,11 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled from 'reshadow'; - -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; interface Props { className?: string; } export const TableBody: React.FC> = function TableBody({ children, className }) { - return styled(BASE_TABLE_STYLES)({children}); + return {children}; }; diff --git a/webapp/packages/core-blocks/src/Table/TableColumnHeader.m.css b/webapp/packages/core-blocks/src/Table/TableColumnHeader.m.css new file mode 100644 index 0000000000..795381ca85 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableColumnHeader.m.css @@ -0,0 +1,29 @@ +.columnHeader { + box-sizing: border-box; + white-space: nowrap; + padding: 16px; + height: 36px; + padding-top: unset; + padding-bottom: unset; + border-color: var(--theme-background); + text-transform: uppercase; + text-align: left; + text-decoration: none !important; + + &.min { + width: 28px; + } + + & > .thFlex { + display: flex; + } + + &.centerContent > .thFlex { + align-items: center; + justify-content: center; + } + + &:last-child { + border-right: none; + } +} diff --git a/webapp/packages/core-blocks/src/Table/TableColumnHeader.tsx b/webapp/packages/core-blocks/src/Table/TableColumnHeader.tsx index cda72a98ab..49c250b12d 100644 --- a/webapp/packages/core-blocks/src/Table/TableColumnHeader.tsx +++ b/webapp/packages/core-blocks/src/Table/TableColumnHeader.tsx @@ -5,9 +5,9 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled, { use } from 'reshadow'; - -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './TableColumnHeader.m.css'; interface Props { title?: string; @@ -25,9 +25,11 @@ export const TableColumnHeader: React.FC> = funct className, children, }) { - return styled(BASE_TABLE_STYLES)( - - {flex ? {children} : children} - , + const styles = useS(style); + + return ( + + {flex ?
{children}
: children} + ); }; diff --git a/webapp/packages/core-blocks/src/Table/TableColumnValue.m.css b/webapp/packages/core-blocks/src/Table/TableColumnValue.m.css new file mode 100644 index 0000000000..2c23361528 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableColumnValue.m.css @@ -0,0 +1,26 @@ +.cell { + position: relative; + box-sizing: border-box; + height: 28px; + padding: 0 16px; + transition: padding ease-in-out 0.24s; + + &.ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &.centerContent > .tdFlex { + align-items: center; + justify-content: center; + } + + & > .tdFlex { + display: flex; + } + + &.expandArea { + padding: 0; + } +} diff --git a/webapp/packages/core-blocks/src/Table/TableColumnValue.tsx b/webapp/packages/core-blocks/src/Table/TableColumnValue.tsx index 1728498cec..631abf388f 100644 --- a/webapp/packages/core-blocks/src/Table/TableColumnValue.tsx +++ b/webapp/packages/core-blocks/src/Table/TableColumnValue.tsx @@ -7,14 +7,15 @@ */ import { observer } from 'mobx-react-lite'; import { forwardRef, useCallback, useContext } from 'react'; -import styled, { use } from 'reshadow'; import { EventContext } from '@cloudbeaver/core-events'; +import { s } from '../s'; import { useObjectRef } from '../useObjectRef'; -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; +import { useS } from '../useS'; import { EventTableItemExpandFlag } from './EventTableItemExpandFlag'; import { EventTableItemSelectionFlag } from './EventTableItemSelectionFlag'; +import style from './TableColumnValue.m.css'; import { TableContext } from './TableContext'; import { TableItemContext } from './TableItemContext'; @@ -34,6 +35,7 @@ export const TableColumnValue = observer( const tableContext = useContext(TableContext); const context = useContext(TableItemContext); const props = useObjectRef({ onClick, onDoubleClick }); + const styles = useS(style); const handleClick = useCallback( (event: React.MouseEvent) => { @@ -61,19 +63,18 @@ export const TableColumnValue = observer( return null; } - return styled(BASE_TABLE_STYLES)( + return ( - {flex && {children}} + {flex &&
{children}
} {!flex && children} - , + ); }), ); diff --git a/webapp/packages/core-blocks/src/Table/TableHeader.m.css b/webapp/packages/core-blocks/src/Table/TableHeader.m.css new file mode 100644 index 0000000000..923a3b1ab0 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableHeader.m.css @@ -0,0 +1,15 @@ +.tableHeader.fixed { + background: var(--theme-surface); + position: sticky; + top: 0; + z-index: 1; + + & > tr { + border-top: none !important; + border-bottom: none !important; + + & > th { + box-shadow: inset 0 -1px 0 var(--theme-background); + } + } +} diff --git a/webapp/packages/core-blocks/src/Table/TableHeader.tsx b/webapp/packages/core-blocks/src/Table/TableHeader.tsx index aaa54f34d6..a13552cd12 100644 --- a/webapp/packages/core-blocks/src/Table/TableHeader.tsx +++ b/webapp/packages/core-blocks/src/Table/TableHeader.tsx @@ -5,9 +5,9 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled, { use } from 'reshadow'; - -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; +import style from './TableHeader.m.css'; interface Props { fixed?: boolean; @@ -15,9 +15,11 @@ interface Props { } export const TableHeader: React.FC> = function TableHeader({ fixed, children, className }) { - return styled(BASE_TABLE_STYLES)( - + const styles = useS(style); + + return ( + {children} - , + ); }; diff --git a/webapp/packages/core-blocks/src/Table/TableItem.m.css b/webapp/packages/core-blocks/src/Table/TableItem.m.css new file mode 100644 index 0000000000..7b14d8df28 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItem.m.css @@ -0,0 +1,19 @@ +.row { + outline: none; + border-bottom: 1px solid; + border-color: var(--theme-background) !important; + + &:last-child { + border-bottom: none; + } + + &:not(.noHover):hover, + &:not(.noHover).selected, + &:not(.noHover).expanded { + background-color: var(--theme-sub-secondary); + } + + &.disabled { + opacity: 0.85; + } +} diff --git a/webapp/packages/core-blocks/src/Table/TableItem.tsx b/webapp/packages/core-blocks/src/Table/TableItem.tsx index f73c8174b8..9cfa042c36 100644 --- a/webapp/packages/core-blocks/src/Table/TableItem.tsx +++ b/webapp/packages/core-blocks/src/Table/TableItem.tsx @@ -7,16 +7,18 @@ */ import { observer } from 'mobx-react-lite'; import { Children, useCallback, useContext, useMemo } from 'react'; -import styled, { use } from 'reshadow'; import { EventContext } from '@cloudbeaver/core-events'; import { getComputed } from '../getComputed'; import { Loader } from '../Loader/Loader'; +import { s } from '../s'; import { useObjectRef } from '../useObjectRef'; -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; +import { useS } from '../useS'; import { EventTableItemSelectionFlag } from './EventTableItemSelectionFlag'; +import cellStyles from './TableColumnValue.m.css'; import { TableContext } from './TableContext'; +import rowStyles from './TableItem.m.css'; import { ITableItemContext, TableItemContext } from './TableItemContext'; interface ExpandProps { @@ -49,6 +51,7 @@ export const TableItem = observer>(function Table }) { const context = useContext(TableContext); const props = useObjectRef({ selectOnItem }); + const styles = useS(rowStyles, cellStyles); if (!context) { throw new Error('TableContext must be provided'); @@ -100,26 +103,25 @@ export const TableItem = observer>(function Table const ExpandElement = expandElement; - return styled(BASE_TABLE_STYLES)( + return ( {children} {isExpanded && ExpandElement && ( - - + + )} - , + ); }); diff --git a/webapp/packages/core-blocks/src/Table/TableItemExpand.m.css b/webapp/packages/core-blocks/src/Table/TableItemExpand.m.css new file mode 100644 index 0000000000..fb53a891d2 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemExpand.m.css @@ -0,0 +1,19 @@ +.tableItemExpandBox { + display: flex; + align-items: center; + cursor: pointer; + width: 16px; + height: 100%; + padding: 0; +} + +.tableItemExpandBoxIcon { + width: 16px; + height: 16px; + padding: 0; + transition: transform ease-in-out 0.2s; + + &.expanded { + transform: rotate(180deg); + } +} diff --git a/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx b/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx index 6d3bdec642..db6c38f650 100644 --- a/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx +++ b/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx @@ -7,17 +7,18 @@ */ import { observer } from 'mobx-react-lite'; import { useCallback, useContext } from 'react'; -import styled, { use } from 'reshadow'; import { EventContext } from '@cloudbeaver/core-events'; import { Icon } from '../Icon'; import { useTranslate } from '../localization/useTranslate'; -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; +import { s } from '../s'; +import { useS } from '../useS'; import { EventTableItemExpandFlag } from './EventTableItemExpandFlag'; import { EventTableItemSelectionFlag } from './EventTableItemSelectionFlag'; import { TableContext } from './TableContext'; import { TableItemContext } from './TableItemContext'; +import style from './TableItemExpand.m.css'; interface Props { onExpand?: (item: any, state: boolean) => void; @@ -29,6 +30,8 @@ export const TableItemExpand = observer(function TableItemExpand({ onExpa const translate = useTranslate(); const tableContext = useContext(TableContext); const context = useContext(TableItemContext); + const styles = useS(style); + if (!context) { throw new Error('TableContext must be provided'); } @@ -51,9 +54,9 @@ export const TableItemExpand = observer(function TableItemExpand({ onExpa [tableContext, context, onExpand, disabled], ); - return styled(BASE_TABLE_STYLES)( - - - , + return ( +
+ +
); }); diff --git a/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx b/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx index 56287d3465..84cceac280 100644 --- a/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx +++ b/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx @@ -12,7 +12,6 @@ import styled, { css } from 'reshadow'; import { EventContext } from '@cloudbeaver/core-events'; import { Checkbox } from '../FormControls/Checkboxes/Checkbox'; -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; import { EventTableItemSelectionFlag } from './EventTableItemSelectionFlag'; import { TableContext } from './TableContext'; import { TableItemContext } from './TableItemContext'; @@ -24,7 +23,7 @@ interface Props { className?: string; } -const checkboxStyles = css` +const styles = css` Checkbox { margin-left: -10px; margin-right: -10px; @@ -34,6 +33,7 @@ const checkboxStyles = css` export const TableItemSelect = observer(function TableItemSelect({ checked, disabled, tooltip, className }) { const tableContext = useContext(TableContext); const context = useContext(TableItemContext); + const handleClick = useCallback( (event: React.MouseEvent) => { if (!context) { @@ -51,10 +51,7 @@ export const TableItemSelect = observer(function TableItemSelect({ checke return null; } - return styled( - BASE_TABLE_STYLES, - checkboxStyles, - )( + return styled(styles)( >(funct onClick, onDoubleClick, }) { - return styled(BASE_TABLE_STYLES)( - - + const styles = useS(rowStyles, cellStyles); + + return ( + + {children} - , + ); }); diff --git a/webapp/packages/core-blocks/src/Table/TableSelect.tsx b/webapp/packages/core-blocks/src/Table/TableSelect.tsx index 6bc441f8e0..fa187c29ae 100644 --- a/webapp/packages/core-blocks/src/Table/TableSelect.tsx +++ b/webapp/packages/core-blocks/src/Table/TableSelect.tsx @@ -11,7 +11,6 @@ import styled, { css } from 'reshadow'; import { Checkbox } from '../FormControls/Checkboxes/Checkbox'; import { useTranslate } from '../localization/useTranslate'; -import { BASE_TABLE_STYLES } from './BASE_TABLE_STYLES'; import { TableContext } from './TableContext'; interface Props { @@ -36,10 +35,7 @@ export const TableSelect = observer(function TableSelect({ id, disabled, throw new Error('Context must be provided'); } - return styled( - BASE_TABLE_STYLES, - styles, - )( + return styled(styles)( >( init: () => T & ThisType, @@ -79,19 +79,21 @@ export function useObservableRef>( return state; }); - if (update) { - runInAction(() => { - assign(state, update); + useLayoutEffect( + action(() => { + if (update) { + assign(state, update); - if (Array.isArray(bind)) { - bind = bind.filter(key => (key as any) in (update as T)); + if (Array.isArray(bind)) { + bind = bind.filter(key => (key as any) in (update as T)); - if (bind.length > 0) { - bindFunctions(state, bind); + if (bind.length > 0) { + bindFunctions(state, bind); + } } } - }); - } + }), + ); return state; } diff --git a/webapp/packages/core-blocks/src/useS.ts b/webapp/packages/core-blocks/src/useS.ts index 94b044d739..f9165d4528 100644 --- a/webapp/packages/core-blocks/src/useS.ts +++ b/webapp/packages/core-blocks/src/useS.ts @@ -5,14 +5,17 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { useContext, useMemo, useRef, useState } from 'react'; +import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useService } from '@cloudbeaver/core-di'; -import { BaseStyles, ComponentStyle, Style, ThemeService } from '@cloudbeaver/core-theming'; +import { BaseStyles, ClassCollection, ComponentStyle, Style, ThemeSelector, ThemeService } from '@cloudbeaver/core-theming'; +import { MetadataMap } from '@cloudbeaver/core-utils'; import { SContextReact } from './SContext'; import { useExecutor } from './useExecutor'; +const stylesCache = new MetadataMap, Promise>>(() => new Map()); + type Intersect = (T extends any ? (x: T) => 0 : never) extends (x: infer R) => 0 ? R extends Record ? BaseStyles @@ -60,6 +63,8 @@ export function useS(...componentStyles: [...T]): Ex ], }); + const staticStyles: BaseStyles[] = []; + const themedStyles: Array> = []; let changed = lastThemeRef.current !== currentThemeId || filteredStyles.length !== stylesRef.current.length; for (let i = 0; !changed && i < filteredStyles.length; i++) { changed = stylesRef.current[i] !== filteredStyles[i]; @@ -68,11 +73,20 @@ export function useS(...componentStyles: [...T]): Ex if (changed) { stylesRef.current = filteredStyles; lastThemeRef.current = currentThemeId; - const staticStyles: BaseStyles[] = []; - const themedStyles: Array> = []; for (const style of filteredStyles) { - const data = typeof style === 'object' ? style : style(currentThemeId); + let data: ClassCollection> | Promise; + + if (typeof style === 'object') { + data = style; + } else { + if (!stylesCache.get(currentThemeId).has(style)) { + data = style(currentThemeId); + stylesCache.get(currentThemeId).set(style, style(currentThemeId)); + } else { + data = stylesCache.get(currentThemeId).get(style)!; + } + } if (data instanceof Promise) { themedStyles.push(data); @@ -81,14 +95,16 @@ export function useS(...componentStyles: [...T]): Ex } } loadedStyles.current = staticStyles.flat(Infinity); + } - if (themedStyles.length > 0) { + useEffect(() => { + if (changed && themedStyles.length > 0) { Promise.all(themedStyles).then(styles => { loadedStyles.current = [staticStyles, styles].flat(Infinity).filter(Boolean) as BaseStyles[]; forceUpdate(patch + 1); }); } - } + }); const styles = useMemo(() => combineStyles(loadedStyles.current), [patch, loadedStyles.current]); diff --git a/webapp/packages/core-blocks/src/useStyles.ts b/webapp/packages/core-blocks/src/useStyles.ts index 78cd5dc26a..b315bf0eec 100644 --- a/webapp/packages/core-blocks/src/useStyles.ts +++ b/webapp/packages/core-blocks/src/useStyles.ts @@ -5,14 +5,17 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { create } from 'reshadow'; import { useService } from '@cloudbeaver/core-di'; -import { BaseStyles, ComponentStyle, Style, ThemeService } from '@cloudbeaver/core-theming'; +import { BaseStyles, ClassCollection, ComponentStyle, Style, ThemeSelector, ThemeService } from '@cloudbeaver/core-theming'; +import { MetadataMap } from '@cloudbeaver/core-utils'; import { useExecutor } from './useExecutor'; +const stylesCache = new MetadataMap, Promise>>(() => new Map()); + /** * Changes styles depending on theme * @@ -21,8 +24,8 @@ import { useExecutor } from './useExecutor'; export function useStyles(...componentStyles: ComponentStyle[]): Record { // todo do you understand that we store ALL STYLES in each component that uses this hook? + const [, forceUpdate] = useState(0); const stylesRef = useRef([]); - const [patch, forceUpdate] = useState(0); const loadedStyles = useRef([]); const themeService = useService(ThemeService); const [currentThemeId, setCurrentThemeId] = useState(() => themeService.currentThemeId); @@ -42,6 +45,8 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record> = []; + const staticStyles: BaseStyles[] = []; let changed = lastThemeRef.current !== currentThemeId || filteredStyles.length !== stylesRef.current.length; for (let i = 0; !changed && i < filteredStyles.length; i++) { changed = stylesRef.current[i] !== filteredStyles[i]; @@ -50,11 +55,20 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record> = []; for (const style of filteredStyles) { - const data = typeof style === 'object' ? style : style(currentThemeId); + let data: ClassCollection> | Promise; + + if (typeof style === 'object') { + data = style; + } else { + if (!stylesCache.get(currentThemeId).has(style)) { + data = style(currentThemeId); + stylesCache.get(currentThemeId).set(style, style(currentThemeId)); + } else { + data = stylesCache.get(currentThemeId).get(style)!; + } + } if (data instanceof Promise) { themedStyles.push(data); @@ -63,16 +77,18 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record 0) { + useEffect(() => { + if (changed && themedStyles.length > 0) { Promise.all(themedStyles).then(styles => { loadedStyles.current = [staticStyles, styles].flat(Infinity).filter(Boolean) as BaseStyles[]; - forceUpdate(patch + 1); + forceUpdate(i => i + 1); }); } - } + }); - const styles = useMemo(() => create(loadedStyles.current), [patch, loadedStyles.current]); + const styles = useMemo(() => create(loadedStyles.current), [loadedStyles.current]); return styles; // todo this method is called in each rerender } diff --git a/webapp/packages/core-cli/configs/babel.config.js b/webapp/packages/core-cli/configs/babel.config.js index 0db4171bbe..ed8f1acecb 100644 --- a/webapp/packages/core-cli/configs/babel.config.js +++ b/webapp/packages/core-cli/configs/babel.config.js @@ -61,6 +61,7 @@ module.exports = api => { [ '@babel/preset-react', { + development: devMode, runtime: 'automatic', }, ], diff --git a/webapp/packages/core-cli/src/babel-plugins/TestingAttributes.ts b/webapp/packages/core-cli/src/babel-plugins/TestingAttributes.ts index 934fe90356..d5a814d96e 100644 --- a/webapp/packages/core-cli/src/babel-plugins/TestingAttributes.ts +++ b/webapp/packages/core-cli/src/babel-plugins/TestingAttributes.ts @@ -16,7 +16,12 @@ import * as t from '@babel/types'; type FunctionType = t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression; function addTestIdAttribute(node: t.JSXOpeningElement, name: string): void { - if (!hasDataAttribute(node, DEFAULT_DATA_TESTID)) { + if ( + t.isJSXIdentifier(node.name) && + node.name.name !== 'Fragment' && + node.name.name !== '_Fragment' && + !hasDataAttribute(node, DEFAULT_DATA_TESTID) + ) { const dataAttribute = createDataAttribute(name, DEFAULT_DATA_TESTID); const indexOf = node.attributes.findIndex(attribute => t.isJSXSpreadAttribute(attribute)); if (indexOf !== -1) { @@ -105,6 +110,9 @@ export default function plugin(): PluginObj { CallExpression(p) { p.traverse({ JSXOpeningElement({ node }) { + if (t.isJSXOpeningFragment(node)) { + return; + } addTestIdAttribute(node, getElementName(node.name)); }, }); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.m.css b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.m.css new file mode 100644 index 0000000000..d8544a0497 --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.m.css @@ -0,0 +1,61 @@ +.body { + flex: 1; + box-sizing: content-box; + display: flex; + max-height: 100%; + overflow: auto; + 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; + } + + &.noBodyPadding .dialogBodyOverflowBox { + padding-right: 0px; + } + + &.noOverflow .dialogBodyContent { + overflow: auto; + } +} + +.dialogBodyOverflow { + composes: branding-overflow from global; + position: sticky; + bottom: 0; + left: 0; + flex-shrink: 0; + width: 100%; + height: 24px; + pointer-events: none; +} + +.dialogBodyOverflowBox { + position: relative; + flex: 1; + box-sizing: border-box; + display: flex; + flex-direction: column; + overflow: auto; + word-break: break-word; + white-space: pre-wrap; + padding-right: 24px; +} + +.dialogBodyContent { + flex: 1; + position: relative; + display: flex; + flex-direction: column; + box-sizing: border-box; +} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.tsx b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.tsx index 178275a055..c083e86d3e 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogBody.tsx @@ -6,13 +6,11 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled, { use } from 'reshadow'; -import { useStyles } from '@cloudbeaver/core-blocks'; +import { s, useS } from '@cloudbeaver/core-blocks'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; -import { dialogStyles } from '../styles'; -import { commonDialogBaseStyle, commonDialogThemeStyle } from './styles'; +import styles from './CommonDialogBody.m.css'; interface Props { noBodyPadding?: boolean; @@ -23,12 +21,14 @@ interface Props { } export const CommonDialogBody = observer(function CommonDialogBody({ noBodyPadding, noOverflow, className, children, style }) { - return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle, dialogStyles, style))( - - - {children} - {!noOverflow && } - - , + const computedStyles = useS(styles, style); + + return ( +
+
+
{children}
+ {!noOverflow &&
} +
+
); }); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.m.css b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.m.css new file mode 100644 index 0000000000..8ef2b4237b --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.m.css @@ -0,0 +1,11 @@ +.footer { + flex-shrink: 0; + padding: 0 24px 24px 24px; + display: flex; + z-index: 0; + box-sizing: border-box; + + &:empty { + display: none; + } +} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx index ca7c9a0cd0..73a3c83406 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogFooter.tsx @@ -6,13 +6,11 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled from 'reshadow'; -import { useStyles } from '@cloudbeaver/core-blocks'; +import { s, useS } from '@cloudbeaver/core-blocks'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; -import { dialogStyles } from '../styles'; -import { commonDialogBaseStyle, commonDialogThemeStyle } from './styles'; +import styles from './CommonDialogFooter.m.css'; interface Props { className?: string; @@ -21,5 +19,7 @@ interface Props { } export const CommonDialogFooter = observer(function CommonDialogFooter({ children, className, style }) { - return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle, dialogStyles, style))(
{children}
); + const computedStyles = useS(styles, style); + + return
{children}
; }); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.m.css b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.m.css new file mode 100644 index 0000000000..2be5ec8633 --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.m.css @@ -0,0 +1,58 @@ +.header { + position: relative; + display: grid; + grid-template-columns: max-content 1fr; + flex-shrink: 0; + padding: 24px; + + &.noPadding { + padding: 0px; + } +} + +.headerTitleContainer { + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + min-height: 24px; + overflow: hidden; +} + +.headerTitle { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.iconContainer { + display: flex; + align-items: center; + justify-content: center; +} + +.icon { + width: 24px; + height: 24px; + margin-right: 16px; + &.bigIcon { + width: 40px; + height: 40px; + } +} + +.subTitle { + composes: theme-typography--caption from global; + grid-column: 2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.reject { + cursor: pointer; + width: 18px; + height: 18px; + flex-shrink: 0; +} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx index 94276b6554..347ada1ff2 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx @@ -6,13 +6,11 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled, { use } from 'reshadow'; -import { Icon, IconOrImage, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; +import { Icon, IconOrImage, s, useS, useTranslate } from '@cloudbeaver/core-blocks'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; -import { dialogStyles } from '../styles'; -import { commonDialogBaseStyle, commonDialogThemeStyle } from './styles'; +import styles from './CommonDialogHeader.m.css'; interface Props { title?: string; @@ -38,19 +36,22 @@ export const CommonDialogHeader = observer(function CommonDialogHeader({ style, }) { const translate = useTranslate(); + const computedStyles = useS(styles, style); - return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle, dialogStyles, style))( -
- {icon && } - -

{translate(title)}

+ return ( +
+
+ {icon && } +
+
+

{translate(title)}

{onReject && ( - +
- +
)} - - {subTitle && {typeof subTitle === 'string' ? translate(subTitle) : subTitle}} -
, +
+ {subTitle &&
{typeof subTitle === 'string' ? translate(subTitle) : subTitle}
} + ); }); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css new file mode 100644 index 0000000000..9cfc79c109 --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css @@ -0,0 +1,64 @@ +.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; + + &.small { + min-width: 404px; + min-height: 262px; + max-height: max(100vh - 48px, 262px); + + &.fixedSize { + width: 404px; + height: 262px; + } + &.fixedWidth { + width: 404px; + } + } + &.medium { + min-width: 576px; + min-height: 374px; + max-height: max(100vh - 48px, 374px); + + &.fixedSize { + width: 576px; + height: 374px; + } + &.fixedWidth { + width: 576px; + } + } + &.large { + min-width: 720px; + min-height: 468px; + max-height: max(100vh - 48px, 468px); + + &.fixedSize { + width: 720px; + height: 468px; + } + &.fixedWidth { + width: 720px; + } + } +} + +.loader { + height: 100%; +} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx index 194f3ec171..beeb8ec6fe 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/CommonDialogWrapper.tsx @@ -8,14 +8,12 @@ import { observer } from 'mobx-react-lite'; import { forwardRef, useContext, useEffect } from 'react'; import { Dialog, useDialogState } from 'reakit/Dialog'; -import styled, { use } from 'reshadow'; -import { Loader, useStyles } from '@cloudbeaver/core-blocks'; +import { Loader, s, useS } from '@cloudbeaver/core-blocks'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { DialogContext } from '../DialogContext'; -import { dialogStyles } from '../styles'; -import { commonDialogBaseStyle, commonDialogThemeStyle } from './styles'; +import styles from './CommonDialogWrapper.m.css'; export interface CommonDialogWrapperProps { size?: 'small' | 'medium' | 'large'; @@ -29,6 +27,7 @@ export interface CommonDialogWrapperProps { export const CommonDialogWrapper = observer( forwardRef(function CommonDialogWrapper({ size = 'medium', fixedSize, fixedWidth, 'aria-label': ariaLabel, className, children, style }, ref) { + const computedStyles = useS(styles, style); const context = useContext(DialogContext); const dialogState = useDialogState({ visible: true }); @@ -38,12 +37,28 @@ export const CommonDialogWrapper = observer - - {children} + return ( + + + + {children} + - , + ); }), ); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts deleted file mode 100644 index 4a6f3cc023..0000000000 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 { css } from 'reshadow'; - -import type { ThemeSelector } from '@cloudbeaver/core-theming'; - -export const commonDialogThemeStyle: ThemeSelector = async theme => { - let styles: any; - - switch (theme) { - case 'dark': - styles = await import('./themes/dark.module.scss'); - break; - default: - styles = await import('./themes/light.module.scss'); - break; - } - - return [styles.default]; -}; - -export const commonDialogContainerStyles = css` - error, - dialog { - composes: theme-background-surface theme-text-on-surface theme-elevation-z10 from global; - border-radius: 0.25rem; - } -`; - -export const commonDialogBaseStyle = [ - commonDialogContainerStyles, - css` - dialog { - display: flex; - flex-direction: column; - position: relative; - overflow: hidden; - margin: 0; - border: none; - height: auto; - max-height: 100%; - max-width: 748px; - padding: 0px; - - &[|size='small'] { - min-width: 404px; - min-height: 262px; - max-height: max(100vh - 48px, 262px); - - &[|fixedSize] { - width: 404px; - height: 262px; - } - &[|fixedWidth] { - width: 404px; - } - } - &[|size='medium'] { - min-width: 576px; - min-height: 374px; - max-height: max(100vh - 48px, 374px); - - &[|fixedSize] { - width: 576px; - height: 374px; - } - &[|fixedWidth] { - width: 576px; - } - } - &[|size='large'] { - min-width: 720px; - min-height: 468px; - max-height: max(100vh - 48px, 468px); - - &[|fixedSize] { - width: 720px; - height: 468px; - } - &[|fixedWidth] { - width: 720px; - } - } - } - header, - dialog-body, - footer { - flex-shrink: 0; - padding: 24px; - - &[|no-padding] { - padding: 0px; - } - } - dialog-body { - padding-top: 0px; - padding-right: 0px; - } - footer { - padding-top: 0px; - } - header { - position: relative; - display: grid; - grid-template-columns: max-content 1fr; - } - header-title { - display: flex; - align-items: center; - justify-content: space-between; - position: relative; - min-height: 24px; - overflow: hidden; - } - icon-container { - display: flex; - align-items: center; - justify-content: center; - } - IconOrImage { - width: 24px; - height: 24px; - margin-right: 16px; - &[|bigIcon] { - width: 40px; - height: 40px; - } - } - dialog-body { - flex: 1; - box-sizing: content-box; - display: flex; - max-height: 100%; - overflow: auto; - } - dialog-body-overflow-box { - position: relative; - flex: 1; - box-sizing: border-box; - display: flex; - flex-direction: column; - overflow: auto; - word-break: break-word; - white-space: pre-wrap; - padding-right: 24px; - } - dialog-body-content { - flex: 1; - position: relative; - display: flex; - flex-direction: column; - box-sizing: border-box; - } - Loader { - height: 100%; - } - dialog-body[|no-padding] + footer { - padding-top: 24px; - } - dialog-body[|no-padding] dialog-body-overflow-box { - padding-right: 0; - } - dialog-body[|no-overflow] dialog-body-content { - overflow: auto; - } - dialog-body-overflow { - composes: branding-overflow from global; - position: sticky; - bottom: 0; - left: 0; - flex-shrink: 0; - width: 100%; - height: 24px; - pointer-events: none; - } - h3 { - margin: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - sub-title { - composes: theme-typography--caption from global; - grid-column: 2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - reject { - cursor: pointer; - width: 18px; - height: 18px; - flex-shrink: 0; - } - footer { - display: flex; - z-index: 0; - box-sizing: border-box; - - &:empty { - display: none; - } - } - `, -]; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/_base-common-dialog.scss b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/_base-common-dialog.scss deleted file mode 100644 index 8eb478f26d..0000000000 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/_base-common-dialog.scss +++ /dev/null @@ -1,15 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -@mixin base-common-dialog() { - :global(.#{$theme-class}) { - dialog-body-overflow { - background-image: linear-gradient(to top, $mdc-theme-surface, rgba($mdc-theme-surface, 0)); - } - } -} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/dark.module.scss b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/dark.module.scss deleted file mode 100644 index 3e1295e285..0000000000 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/dark.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 "@cloudbeaver/core-theming/src/styles/theme-dark"; -@import "base-common-dialog"; - -@include base-common-dialog; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/light.module.scss b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/light.module.scss deleted file mode 100644 index 10d9953980..0000000000 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/themes/light.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 "@cloudbeaver/core-theming/src/styles/theme-light"; -@import "base-common-dialog"; - -@include base-common-dialog; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.m.css b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.m.css new file mode 100644 index 0000000000..25fe6fea27 --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.m.css @@ -0,0 +1,4 @@ +.footer { + align-items: center; + gap: 16px; +} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx index 1db91e26ca..4cae3d4fb7 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx @@ -5,9 +5,7 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import styled, { css } from 'reshadow'; - -import { Button, Translate, useFocus, useStyles } from '@cloudbeaver/core-blocks'; +import { Button, Fill, s, Translate, useFocus, useS } from '@cloudbeaver/core-blocks'; import type { TLocalizationToken } from '@cloudbeaver/core-localization'; import { CommonDialogBody } from './CommonDialog/CommonDialogBody'; @@ -15,17 +13,7 @@ import { CommonDialogFooter } from './CommonDialog/CommonDialogFooter'; import { CommonDialogHeader } from './CommonDialog/CommonDialogHeader'; import { CommonDialogWrapper } from './CommonDialog/CommonDialogWrapper'; import type { DialogComponent, DialogueStateResult } from './CommonDialogService'; - -const style = css` - CommonDialogFooter { - align-items: center; - gap: 16px; - } - - fill { - flex: 1; - } -`; +import style from './ConfirmationDialog.m.css'; export interface ConfirmationDialogPayload { icon?: string; @@ -46,20 +34,21 @@ export const ConfirmationDialog: DialogComponent({ focusFirstChild: true }); const { icon, title, subTitle, bigIcon, viewBox, message, confirmActionText, cancelActionText } = payload; - return styled(useStyles(style))( + return ( - + - + {payload.extraStatus !== undefined && ( - , + ); }; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.m.css b/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.m.css new file mode 100644 index 0000000000..90f5ead351 --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.m.css @@ -0,0 +1,35 @@ +.loader { + height: 100%; +} + +.backdrop { + box-sizing: border-box; + background-color: rgba(0, 0, 0, 0.48); + /*backdrop-filter: blur(4px); + background-color: rgba(221, 221, 221, 0.25);*/ + position: fixed; + top: 0px; + right: 0px; + bottom: 0px; + left: 0px; + z-index: 999; + display: flex; + overflow: auto; +} + +.innerBox { + display: flex; + margin: auto; + padding: 24px; + flex-direction: column; + align-items: center; + + & > :not(:last-child) { + 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-dialogs/src/CommonDialog/DialogsPortal.tsx b/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx index 53adecf4ae..f7b8d7c96b 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx @@ -8,18 +8,16 @@ import { observer } from 'mobx-react-lite'; import { useLayoutEffect, useMemo, useRef } from 'react'; import { DialogBackdrop } from 'reakit/Dialog'; -import styled from 'reshadow'; -import { ErrorBoundary, Loader, useObjectRef, useStyles } from '@cloudbeaver/core-blocks'; +import { ErrorBoundary, Loader, s, useObjectRef, useS } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; -import { commonDialogContainerStyles } from './CommonDialog/styles'; import { CommonDialogService, DialogInternal } from './CommonDialogService'; import { DialogContext, IDialogContext } from './DialogContext'; -import { dialogStyles } from './styles'; +import style from './DialogsPortal.m.css'; export const DialogsPortal = observer(function DialogsPortal() { - const styles = useStyles(dialogStyles); + const styles = useS(style); const commonDialogService = useService(CommonDialogService); const focusedElementRef = useRef(null); @@ -81,12 +79,12 @@ export const DialogsPortal = observer(function DialogsPortal() { }; }, [activeDialog]); - return styled(styles)( - - - + return ( + + +
{commonDialogService.dialogs.map((dialog, i, arr) => ( - + ))} - +
-
, +
); }); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.m.css b/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.m.css new file mode 100644 index 0000000000..060df06e60 --- /dev/null +++ b/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.m.css @@ -0,0 +1,3 @@ +.footer { + align-items: center; +} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx b/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx index bc401781f8..ab2b931eff 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx @@ -8,9 +8,8 @@ import { observable } from 'mobx'; import { observer } from 'mobx-react-lite'; import { useEffect } from 'react'; -import styled, { css } from 'reshadow'; -import { Button, Container, InputField, SubmittingForm, useFocus, useObservableRef, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, Container, Fill, InputField, s, SubmittingForm, useFocus, useObservableRef, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { throttleAsync } from '@cloudbeaver/core-utils'; import { CommonDialogBody } from './CommonDialog/CommonDialogBody'; @@ -18,16 +17,7 @@ import { CommonDialogFooter } from './CommonDialog/CommonDialogFooter'; import { CommonDialogHeader } from './CommonDialog/CommonDialogHeader'; import { CommonDialogWrapper } from './CommonDialog/CommonDialogWrapper'; import type { DialogComponent } from './CommonDialogService'; - -const style = css` - CommonDialogFooter { - align-items: center; - } - - fill { - flex: 1; - } -`; +import style from './RenameDialog.m.css'; interface IRenameDialogState { value: string; @@ -59,6 +49,7 @@ export const RenameDialog: DialogComponent = observ }) { const translate = useTranslate(); const [focusedRef] = useFocus({ focusFirstChild: true }); + const styles = useS(style); const { icon, subTitle, bigIcon, viewBox, value, objectName, create, confirmActionText } = payload; let { title } = payload; @@ -102,7 +93,7 @@ export const RenameDialog: DialogComponent = observ const errorMessage = state.valid ? ' ' : translate(state.message ?? 'ui_rename_taken_or_invalid'); - return styled(useStyles(style))( + return ( @@ -114,15 +105,15 @@ export const RenameDialog: DialogComponent = observ - + - + - , + ); }); diff --git a/webapp/packages/core-dialogs/src/CommonDialog/styles.ts b/webapp/packages/core-dialogs/src/CommonDialog/styles.ts deleted file mode 100644 index 4e02b53727..0000000000 --- a/webapp/packages/core-dialogs/src/CommonDialog/styles.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 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 { css } from 'reshadow'; - -export const dialogStyles = css` - DialogBackdrop { - box-sizing: border-box; - background-color: rgba(0, 0, 0, 0.48); - /*backdrop-filter: blur(4px); - background-color: rgba(221, 221, 221, 0.25);*/ - position: fixed; - top: 0px; - right: 0px; - bottom: 0px; - left: 0px; - z-index: 999; - display: flex; - overflow: auto; - } - - inner-box { - display: flex; - margin: auto; - padding: 24px; - flex-direction: column; - align-items: center; - - & > :not(:last-child) { - display: none; - } - } - - Dialog { - box-sizing: border-box; - display: flex; - outline: none; - } -`; diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts index 4dc08de783..50ea2fb1ff 100644 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts +++ b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts @@ -36,7 +36,6 @@ export class ContextMenu { } constructMenuWithContext(panelId: string, context: IMenuContext): IMenuPanel { - context.contextId = context.contextId || uuid(); return this.constructMenuPanelWithContext(panelId, context); } @@ -59,7 +58,8 @@ export class ContextMenu { private constructMenuPanelWithContext(panelId: string, context: IMenuContext): IMenuPanel { const panel = this.menuStore.getPanel(panelId); - return new ContextMenuPanel(`${panelId}-${context.contextId!}-panel`, () => this.constructMenuItems(panel.menuItems.values, context)); + const contextId = context.contextId || uuid(); + return new ContextMenuPanel(`${panelId}-${contextId}-panel`, () => this.constructMenuItems(panel.menuItems.values, context)); } private constructMenuItems(menuItems: Array>, context: IMenuContext): ComputedMenuItemModel[] { diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts index e6c37f9ed9..626ca1650d 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts @@ -170,18 +170,14 @@ export class NavTreeResource extends CachedMapResource, state: boolean): void { - ResourceKeyUtils.forEach(keyObject, key => { - const children = resourceKeyList(this.getNestedChildren(key)); - this.navNodeInfoResource.setDetails(children, state); - - ResourceKeyUtils.forEach(children, key => { - const metadata = this.metadata.get(key); + const list = resourceKeyList(ResourceKeyUtils.mapArray(keyObject, key => this.getNestedChildren(key)).flat()); + this.navNodeInfoResource.setDetails(list, state); - if (!metadata.withDetails && state) { - metadata.outdated = true; - } - metadata.withDetails = state; - }); + this.updateMetadata(list, metadata => { + if (!metadata.withDetails && state) { + metadata.outdated = true; + } + metadata.withDetails = state; }); } @@ -501,7 +497,7 @@ export class NavTreeResource extends CachedMapResource node.id)]), metadata.withDetails); } @@ -519,7 +515,7 @@ export class NavTreeResource extends CachedMapResource this.insertSlice(data, offset, limit)), ); } else { - const metadata = this.metadata.get(data.parentPath); + const metadata = this.getMetadata(data.parentPath); this.setDetails(resourceKeyList([data.navNodeInfo.id, ...data.navNodeChildren.map(node => node.id)]), metadata.withDetails); @@ -547,7 +543,7 @@ export class NavTreeResource extends CachedMapResource { - const metadata = this.metadata.get(parentPath); + const metadata = this.getMetadata(parentPath); const { navNodeChildren, navNodeInfo } = await this.graphQLService.sdk.navNodeChildren({ parentPath, offset, diff --git a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.m.css b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.m.css new file mode 100644 index 0000000000..a3c5c590cb --- /dev/null +++ b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.m.css @@ -0,0 +1,27 @@ +.code, +.message { + display: block; + white-space: pre-wrap; +} +.textarea { + min-height: 270px !important; +} +.message { + overflow: auto; + max-height: 96px; +} +.property { + padding: 8px 16px; +} +.errorDetails { + border: solid 1px; +} +.footer { + align-items: center; + justify-content: flex-end; + gap: 24px; +} +.errorInfoContainer { + display: flex; + flex-direction: column; +} diff --git a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx index dea16631ce..e1ac728334 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx @@ -7,47 +7,51 @@ */ import { observer } from 'mobx-react-lite'; import { useCallback, useMemo } from 'react'; -import styled from 'reshadow'; -import { Button, Iframe, Textarea, useClipboard, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, Iframe, s, Textarea, useClipboard, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, DialogComponent } from '@cloudbeaver/core-dialogs'; +import style from './ErrorDetailsDialog.m.css'; import { ErrorModel, IErrorInfo } from './ErrorModel'; -import { styles } from './styles'; function DisplayErrorInfo({ error }: { error: IErrorInfo }) { - return styled(useStyles(styles))( + const styles = useS(style); + + return ( <> - {error.isHtml ?