From f294ca776755b41297f4220cea8b7efee0c0fee9 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 13 Jun 2023 19:50:43 +0400 Subject: [PATCH] feat: CB-3517 css-modules for styling components (#1732) * feat: CB-3517 css-modules for styling components * CB-3517 refactor: use new styles system for Menu * CB-3517 refactor: new styling system * CB-3517 fix: log viewer styles * CB-3517 chore: remove extra styles * CB-3517 fix: context menu offset * CB-3517 chore: review code cleanup --- .vscode/settings.json | 1 + .../ConnectionImageWithMaskSvg.m.css | 3 + .../ConnectionImageWithMaskSvg.tsx | 5 + .../src/Containers/BASE_CONTAINERS_STYLES.ts | 14 +- .../src/Containers/IContainerProps.ts | 1 + .../Containers/filterContainerFakeProps.ts | 2 +- .../packages/core-blocks/src/DisplayError.tsx | 2 +- .../core-blocks/src/ExceptionMessage.tsx | 5 +- .../src/FormControls/BASE_DROPDOWN_STYLES.ts | 2 +- .../Checkboxes/CheckboxMarkup.m.css | 2 + .../Checkboxes/CheckboxMarkup.tsx | 8 +- .../FormControls/Checkboxes/FieldCheckbox.tsx | 4 +- .../core-blocks/src/FormControls/Combobox.tsx | 4 +- .../src/HTMLTemplates/sso.html.ejs | 16 +- webapp/packages/core-blocks/src/InfoItem.tsx | 2 +- .../core-blocks/src/ItemList/styles.ts | 10 +- .../packages/core-blocks/src/Menu/Menu.m.css | 28 +++ webapp/packages/core-blocks/src/Menu/Menu.tsx | 54 +++-- .../core-blocks/src/Menu/MenuEmptyItem.tsx | 20 +- .../core-blocks/src/Menu/MenuItem.m.css | 23 +++ .../core-blocks/src/Menu/MenuItem.tsx | 64 +++--- .../core-blocks/src/Menu/MenuItemCheckbox.tsx | 34 ++++ .../src/Menu/MenuItemElement.m.css | 70 +++++++ .../core-blocks/src/Menu/MenuItemElement.tsx | 43 ++-- .../core-blocks/src/Menu/MenuItemRadio.tsx | 34 ++++ .../core-blocks/src/Menu/MenuPanel.m.css | 27 +++ .../core-blocks/src/Menu/MenuPanel.tsx | 31 +-- .../core-blocks/src/Menu/MenuSeparator.m.css | 11 ++ .../core-blocks/src/Menu/MenuSeparator.tsx | 19 ++ .../src/Menu/ReakitProxyComponent.ts | 25 +++ .../core-blocks/src/Menu/menuPanelStyles.ts | 186 ------------------ .../src/Overlay/OVERLAY_BASE_STYLES.ts | 2 +- .../src/PropertiesTable/PropertyItem.tsx | 2 +- webapp/packages/core-blocks/src/SContext.tsx | 31 +++ .../core-blocks/src/Split/SplitControls.m.css | 123 ++++++++++++ .../core-blocks/src/Split/SplitControls.tsx | 25 +-- .../src/Split/splitButtonStyles.ts | 131 ------------ .../src/Table/BASE_TABLE_STYLES.ts | 10 +- .../core-blocks/src/Table/TableItemSelect.tsx | 4 +- .../core-blocks/src/Table/TableSelect.tsx | 4 +- webapp/packages/core-blocks/src/Tags/Tags.tsx | 2 +- .../core-blocks/src/ToolsPanel/ToolsPanel.tsx | 2 +- .../src/Tree/TreeNode/TreeNodeStyles.ts | 30 +-- webapp/packages/core-blocks/src/index.ts | 13 +- .../core-blocks/src/layout/AppLogo.tsx | 6 +- .../core-blocks/src/layout/TopAppBar.tsx | 2 +- webapp/packages/core-blocks/src/s.ts | 12 ++ webapp/packages/core-blocks/src/useS.ts | 108 ++++++++++ webapp/packages/core-blocks/src/useStyles.ts | 11 +- .../packages/core-cli/configs/babel.config.js | 9 +- .../core-cli/configs/webpack.config.js | 30 ++- .../configs/webpack.product.dev.config.js | 8 +- .../core-cli/global.d.ts} | 7 +- webapp/packages/core-cli/package.json | 3 +- .../src/CommonDialog/CommonDialog/styles.ts | 2 +- .../core-dialogs/src/CommonDialog/styles.ts | 2 +- .../ErrorDetailsDialog/styles.ts | 2 +- .../core-theming/src/ComponentStyle.ts | 12 +- .../packages/core-theming/src/ThemeService.ts | 38 ++++ .../core-theming/src/styles/_button.scss | 6 +- .../core-theming/src/styles/_checkbox.scss | 19 +- .../core-theming/src/styles/_radio.scss | 9 +- .../core-theming/src/styles/_switch.scss | 12 +- .../src/styles/theme-dark.module.scss | 2 +- .../src/styles/theme-light.module.scss | 2 +- .../packages/core-theming/src/themeUtils.ts | 6 +- .../core-ui/src/ContextMenu/ContextMenu.tsx | 36 +--- .../src/ContextMenu/IContextMenuItemProps.ts | 5 - .../src/ContextMenu/MenuActionElement.tsx | 32 ++- .../MenuBar/MENU_BAR_DEFAULT_STYLES.ts | 70 ------- .../MenuBar/MENU_BAR_SMALL_STYLES.ts | 31 --- .../src/ContextMenu/MenuBar/MenuBar.m.css | 19 ++ .../src/ContextMenu/MenuBar/MenuBar.tsx | 93 ++++----- .../src/ContextMenu/MenuBar/MenuBarItem.m.css | 78 ++++++++ .../src/ContextMenu/MenuBar/MenuBarItem.tsx | 49 +++-- .../src/ContextMenu/MenuItemRenderer.tsx | 65 ++---- .../src/ContextMenu/SubMenuElement.tsx | 46 +---- .../core-ui/src/InlineEditor/EditorDialog.tsx | 2 +- .../core-ui/src/SideBarPanel/SideBarPanel.tsx | 4 - .../core-ui/src/Tabs/Tab/BASE_TAB_STYLES.ts | 14 +- .../src/Tabs/Tab/UnderlineTabStyles.ts | 4 +- .../core-ui/src/Tabs/Tab/verticalTabStyles.ts | 2 +- webapp/packages/core-ui/src/index.ts | 4 +- webapp/packages/core-utils/src/clsx.ts | 2 +- .../src/Menu/MenuItem/IMenuBaseItem.ts | 3 - .../src/Menu/MenuItem/IMenuSubMenuItem.ts | 3 - .../packages/eslint-config/eslint-config.json | 16 +- .../src/Administration/Administration.tsx | 2 +- .../AdministrationMenu/AdministrationMenu.tsx | 17 +- .../AdministrationTopAppBar.tsx | 8 +- .../ConfigurationWizard/WizardStepper.tsx | 2 +- .../ConfigurationWizard/Finish/FinishPage.tsx | 2 +- .../src/PluginBootstrap.ts | 7 +- .../AuthConfigurationForm.tsx | 11 +- .../AuthConfigurationsAdministration.tsx | 2 +- .../CreateAuthConfiguration.tsx | 4 +- .../MetaParameters/CreateMetaParameter.tsx | 4 +- .../MetaParameters/Form/MetaParameterForm.tsx | 11 +- .../Form/UserInfo/MetaParameterInfoTab.tsx | 4 +- .../Administration/Users/Teams/CreateTeam.tsx | 4 +- .../Administration/Users/Teams/TeamForm.tsx | 11 +- .../Users/Teams/TeamsTable/TeamsTable.tsx | 2 +- .../Users/UserForm/UserForm.tsx | 11 +- .../Users/UsersAdministration.tsx | 12 +- .../Users/UsersTable/CreateUser.tsx | 4 +- .../Users/UsersTable/UsersPage.tsx | 2 +- .../src/Dialog/AuthDialog.tsx | 1 - .../AuthProviderForm/ConfigurationsList.tsx | 2 +- .../Connections/ConnectionsAdministration.tsx | 3 +- .../ConnectionsTable/Connection.tsx | 4 +- .../CreateConnection/CreateConnection.tsx | 8 +- .../src/ConnectionForm/ConnectionForm.tsx | 13 +- .../src/Dialog/OutputOptionsForm.tsx | 36 ++-- .../src/Dialog/ProcessorConfigureDialog.tsx | 9 +- .../src/Dialog/ProcessorSelectDialog.tsx | 2 +- .../FilterCustomValueDialog.tsx | 2 +- .../src/styles/base.scss | 3 + .../src/DVResultSetGroupingPresentation.tsx | 2 +- .../src/DataViewerPage/DataViewerPanel.tsx | 2 +- .../src/TableViewer/TableError.tsx | 2 +- .../AutoRefresh/AutoRefreshButton.tsx | 12 +- .../AutoRefresh/AutoRefreshSettingsDialog.tsx | 4 +- .../TableViewer/TableFooter/TableFooter.tsx | 4 +- .../TableFooterMenu/TableFooterMenu.m.css | 4 + .../TableFooterMenu/TableFooterMenu.tsx | 32 +-- .../TableHeader/TableHeaderMenu.tsx | 67 +------ .../src/TableViewer/TableViewer.tsx | 4 +- .../src/TableViewer/ValuePanel/ValuePanel.tsx | 9 - .../TextValue/TextValuePresentation.tsx | 2 +- .../VALUE_PANEL_TOOLS_STYLES.ts | 2 +- ...gin_datasource_context_switch_database.svg | 2 +- ...n_datasource_context_switch_database_m.svg | 2 +- ..._datasource_context_switch_database_sm.svg | 2 +- .../ConnectionSelector/ConnectionIcon.tsx | 43 ++-- .../ConnectionImageWithMask.m.css | 3 + .../src/DdlViewer/DDLViewerTabPanel.m.css | 17 ++ .../src/DdlViewer/DDLViewerTabPanel.tsx | 19 +- .../ExtendedDDLViewerTabPanel.tsx | 24 ++- .../plugin-ddl-viewer/src/TAB_PANEL_STYLES.ts | 2 +- .../SearchResourceMenuItemComponent.tsx | 1 - .../src/LogViewer/LogViewerTable.tsx | 4 +- .../ElementsTreeMenuBarItem.m.css | 5 + .../ElementsTreeToolsMenu.m.css | 4 + .../ElementsTreeToolsMenu.tsx | 92 +++------ .../TreeNodeMenu/treeNodeMenuStyles.ts | 1 - .../src/NavigationTree/NavigationTree.tsx | 2 +- .../ObjectPropertiesPagePanel.tsx | 2 +- .../Table/Columns/ColumnIcon/ColumnIcon.ts | 6 +- .../Columns/ColumnSelect/ColumnSelect.ts | 6 +- .../ColumnSelect/SelectorFormatter.tsx | 4 +- .../ObjectPropertyTable/Table/Table.tsx | 4 +- .../Table/styles/base.scss | 6 +- .../ObjectPropertyTable/Table/useTableData.ts | 4 +- .../formControls/ObjectPropertyCheckbox.tsx | 2 +- .../src/Tree/ResourceManagerTree.tsx | 2 +- .../DataSynchronizationNotification.tsx | 2 +- .../ServerNodeChangedDialog.tsx | 2 +- .../SessionExpiredDialog.tsx | 2 +- .../src/SettingsMenu/SettingsMenu.tsx | 9 +- .../src/SettingsMenu/settingsMenuStyles.ts | 4 +- .../src/SqlEditor/SqlEditor.tsx | 2 +- .../src/SqlEditor/SqlEditorActionsMenu.tsx | 35 +++- .../SqlEditor/SqlEditorActionsMenuBar.m.css | 4 + .../SqlEditorActionsMenuBarItem.m.css | 3 + .../src/SqlEditor/SqlEditorToolsMenu.tsx | 34 +++- .../src/SqlResultTabs/SqlResultSetPanel.tsx | 2 +- .../src/SqlResultTabs/SqlResultTabs.tsx | 2 +- .../src/ScriptPreview/ScriptPreviewDialog.tsx | 2 +- .../src/SqlGenerators/GeneratedSqlDialog.tsx | 4 +- .../src/ToolsPanel/ToolsPanel.tsx | 16 +- .../plugin-top-app-bar/src/PluginBootstrap.ts | 4 +- .../TopNavBar/AppStateMenu/AppStateMenu.m.css | 9 + .../TopNavBar/AppStateMenu/AppStateMenu.tsx | 51 ++--- .../src/TopNavBar/MainMenu/MainMenu.tsx | 18 +- .../src/TopNavBar/PublicTopNavBar.tsx | 20 ++ .../src/TopNavBar/TopNavBar.tsx | 91 ++++++++- .../src/TopNavBar/shared/TopMenu.m.css | 13 ++ .../src/TopNavBar/shared/TopMenuBar.m.css | 4 + .../src/TopNavBar/shared/TopMenuBarItem.m.css | 61 ++++++ .../TopNavBar/shared/TopMenuCheckbox.m.css | 3 + .../src/TopNavBar/shared/TopMenuItem.m.css | 15 ++ .../src/TopNavBar/shared/TopMenuItem.tsx | 48 ----- .../TopNavBar/shared/TopMenuItemElement.m.css | 3 + .../src/TopNavBar/shared/TopMenuPanel.m.css | 10 + .../TopNavBar/shared/TopMenuSeparator.m.css | 3 + .../src/TopNavBar/shared/TopMenuWrapper.m.css | 4 + .../src/TopNavBar/shared/TopNavButton.tsx | 79 -------- .../src/TopNavBar/shared/topMenuStyles.ts | 154 --------------- .../src/TopNavBar/styles.ts | 44 ----- .../packages/plugin-top-app-bar/src/index.ts | 4 +- .../plugin-user-profile/src/UserInfo.tsx | 16 +- .../src/UserMenu/UserMenu.m.css | 14 ++ .../src/UserMenu/UserMenu.tsx | 15 +- .../src/UserMenu/userMenuStyles.ts | 26 --- .../AuthProviders/UserAuthProvidersTab.tsx | 4 +- .../AuthTokens/UserAuthProvidersTab.tsx | 4 +- .../Authentication/AuthenticationTab.tsx | 4 +- .../src/UserProfileForm/UserForm.tsx | 11 +- .../UserProfileForm/UserInfo/UserInfoTab.tsx | 4 +- webapp/tsconfig.json | 18 +- 200 files changed, 1758 insertions(+), 1742 deletions(-) create mode 100644 webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css create mode 100644 webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.m.css create mode 100644 webapp/packages/core-blocks/src/Menu/Menu.m.css create mode 100644 webapp/packages/core-blocks/src/Menu/MenuItem.m.css create mode 100644 webapp/packages/core-blocks/src/Menu/MenuItemCheckbox.tsx create mode 100644 webapp/packages/core-blocks/src/Menu/MenuItemElement.m.css create mode 100644 webapp/packages/core-blocks/src/Menu/MenuItemRadio.tsx create mode 100644 webapp/packages/core-blocks/src/Menu/MenuPanel.m.css create mode 100644 webapp/packages/core-blocks/src/Menu/MenuSeparator.m.css create mode 100644 webapp/packages/core-blocks/src/Menu/MenuSeparator.tsx create mode 100644 webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts delete mode 100644 webapp/packages/core-blocks/src/Menu/menuPanelStyles.ts create mode 100644 webapp/packages/core-blocks/src/SContext.tsx create mode 100644 webapp/packages/core-blocks/src/Split/SplitControls.m.css delete mode 100644 webapp/packages/core-blocks/src/Split/splitButtonStyles.ts create mode 100644 webapp/packages/core-blocks/src/s.ts create mode 100644 webapp/packages/core-blocks/src/useS.ts rename webapp/{typings.d.ts => packages/core-cli/global.d.ts} (90%) delete mode 100644 webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts delete mode 100644 webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES.ts create mode 100644 webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.m.css create mode 100644 webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.m.css create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.m.css create mode 100644 webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionImageWithMask.m.css create mode 100644 webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerTabPanel.m.css create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeMenuBarItem.m.css create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenu.m.css create mode 100644 webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorActionsMenuBar.m.css create mode 100644 webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorActionsMenuBarItem.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/PublicTopNavBar.tsx create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenu.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuBar.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuBarItem.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuCheckbox.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuItem.m.css delete mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuItem.tsx create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuItemElement.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuPanel.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuSeparator.m.css create mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuWrapper.m.css delete mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopNavButton.tsx delete mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/topMenuStyles.ts delete mode 100644 webapp/packages/plugin-top-app-bar/src/TopNavBar/styles.ts create mode 100644 webapp/packages/plugin-user-profile/src/UserMenu/UserMenu.m.css delete mode 100644 webapp/packages/plugin-user-profile/src/UserMenu/userMenuStyles.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index c176db36b8..3a4e57c664 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.tabSize": 2, "editor.formatOnSave": true, + "editor.linkedEditing": true, "task.autoDetect": "off", diff --git a/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css b/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css new file mode 100644 index 0000000000..0f158389b2 --- /dev/null +++ b/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css @@ -0,0 +1,3 @@ +.background { + fill: transparent; +} diff --git a/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.tsx b/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.tsx index 7906f0517a..23014f2bdd 100644 --- a/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.tsx +++ b/webapp/packages/core-blocks/src/ConnectionImageWithMask/ConnectionImageWithMaskSvg.tsx @@ -7,6 +7,9 @@ */ import { GlobalConstants, isValidUrl } from '@cloudbeaver/core-utils'; +import { useS } from '../useS'; +import style from './ConnectionImageWithMaskSvg.m.css'; + interface Props { icon?: string; connected: boolean; @@ -18,6 +21,7 @@ interface Props { } export const ConnectionImageWithMaskSvg: React.FC = ({ icon, connected, maskId, size = 16, markerRadius = 4, paddingSize = 0, className }) => { + const styles = useS(style); if (!icon) { return null; } @@ -48,6 +52,7 @@ export const ConnectionImageWithMaskSvg: React.FC = ({ icon, connected, m mask={`url(#${maskId})`} /> + ); diff --git a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts index db39c41229..4759cd171f 100644 --- a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts +++ b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts @@ -49,6 +49,10 @@ export const BASE_CONTAINERS_STYLES = css` align-items: flex-start; } + &[flexEnd] { + align-items: flex-end; + } + &[center] { align-items: center; justify-content: center; @@ -64,7 +68,7 @@ export const BASE_CONTAINERS_STYLES = css` } &[parent] { - padding: 24px; + padding: 16px; &[compact] { padding: 16px; @@ -76,14 +80,14 @@ export const BASE_CONTAINERS_STYLES = css` } &[gap] { - gap: 24px; + gap: 16px; &[compact] { gap: 16px; } &[dense] { - gap: 8px; + gap: 16px; } } } @@ -91,7 +95,7 @@ export const BASE_CONTAINERS_STYLES = css` Group { align-content: baseline; box-sizing: border-box; - padding: 24px; + padding: 16px; border-radius: var(--theme-group-element-radius); &[dense] { @@ -217,7 +221,7 @@ export const BASE_CONTAINERS_STYLES = css` } Group[box] > GroupTitle { - padding: 24px; + padding: 16px; } GroupSubTitle { diff --git a/webapp/packages/core-blocks/src/Containers/IContainerProps.ts b/webapp/packages/core-blocks/src/Containers/IContainerProps.ts index 5839ff3f47..f1349a2987 100644 --- a/webapp/packages/core-blocks/src/Containers/IContainerProps.ts +++ b/webapp/packages/core-blocks/src/Containers/IContainerProps.ts @@ -10,6 +10,7 @@ import type { ILayoutSizeProps } from './ILayoutSizeProps'; export interface IContainerProps extends ILayoutSizeProps { hideEmpty?: boolean; flexStart?: boolean; + flexEnd?: boolean; baseline?: boolean; center?: boolean; vertical?: boolean; diff --git a/webapp/packages/core-blocks/src/Containers/filterContainerFakeProps.ts b/webapp/packages/core-blocks/src/Containers/filterContainerFakeProps.ts index acb850f498..4fa5846c44 100644 --- a/webapp/packages/core-blocks/src/Containers/filterContainerFakeProps.ts +++ b/webapp/packages/core-blocks/src/Containers/filterContainerFakeProps.ts @@ -9,7 +9,7 @@ import { filterLayoutFakeProps } from './filterLayoutFakeProps'; import type { IContainerProps } from './IContainerProps'; export function filterContainerFakeProps(props: T): Omit { - const { hideEmpty, flexStart, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...rest } = + const { hideEmpty, flexStart, flexEnd, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...rest } = filterLayoutFakeProps(props); return rest as Omit; diff --git a/webapp/packages/core-blocks/src/DisplayError.tsx b/webapp/packages/core-blocks/src/DisplayError.tsx index f3d130246c..6635e2e045 100644 --- a/webapp/packages/core-blocks/src/DisplayError.tsx +++ b/webapp/packages/core-blocks/src/DisplayError.tsx @@ -29,7 +29,7 @@ const style = css` error-inner-block { display: flex; margin: auto; - padding: 16px 24px; + padding: 8px 16px; flex-direction: column; align-items: center; } diff --git a/webapp/packages/core-blocks/src/ExceptionMessage.tsx b/webapp/packages/core-blocks/src/ExceptionMessage.tsx index 4fd80e2c96..4221a9794f 100644 --- a/webapp/packages/core-blocks/src/ExceptionMessage.tsx +++ b/webapp/packages/core-blocks/src/ExceptionMessage.tsx @@ -21,7 +21,7 @@ const style = css` error { flex: 1; display: flex; - padding: 24px; + padding: 16px; } error-name { @@ -30,6 +30,7 @@ const style = css` text-overflow: ellipsis; overflow: hidden; white-space: nowrap; + margin: 0; & span { display: inline-block; @@ -148,7 +149,7 @@ export const ExceptionMessage = observer(function ExceptionMessage({ exce {!icon && ( <> - + {translate('core_blocks_exception_message_error_title')} diff --git a/webapp/packages/core-blocks/src/FormControls/BASE_DROPDOWN_STYLES.ts b/webapp/packages/core-blocks/src/FormControls/BASE_DROPDOWN_STYLES.ts index 1f29199361..569406b839 100644 --- a/webapp/packages/core-blocks/src/FormControls/BASE_DROPDOWN_STYLES.ts +++ b/webapp/packages/core-blocks/src/FormControls/BASE_DROPDOWN_STYLES.ts @@ -32,7 +32,7 @@ export const BASE_DROPDOWN_STYLES = css` outline: none; color: inherit; cursor: pointer; - gap: 8px; + gap: 16px; & item-icon, & item-title { diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.m.css b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.m.css new file mode 100644 index 0000000000..c88e369e63 --- /dev/null +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.m.css @@ -0,0 +1,2 @@ +.checkbox { +} diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx index 42c8ef1868..4acf4fd0ff 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx @@ -10,7 +10,10 @@ import styled, { css } from 'reshadow'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; +import { s } from '../../s'; +import { useS } from '../../useS'; import { useStyles } from '../../useStyles'; +import CheckboxMarkupStyles from './CheckboxMarkup.m.css'; export type CheckboxMod = 'primary' | 'surface' | 'small'; @@ -95,6 +98,8 @@ interface ICheckboxMarkupProps extends Omit = function CheckboxMarkup({ id, label, @@ -107,6 +112,7 @@ export const CheckboxMarkup: React.FC = function CheckboxM readOnly, ...rest }) { + const styles = useS(CheckboxMarkupStyles); const checkboxRef = useRef(null); useLayoutEffect(() => { @@ -125,7 +131,7 @@ export const CheckboxMarkup: React.FC = function CheckboxM ), )( - + diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx index e78b506ca6..ac811b0e0d 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx @@ -13,13 +13,11 @@ import { isControlPresented } from '../isControlPresented'; import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox'; const style = css` - Checkbox { - margin: -10px; - } field { display: flex; align-items: center; white-space: pre-wrap; + height: 32px; & field-label { cursor: pointer; diff --git a/webapp/packages/core-blocks/src/FormControls/Combobox.tsx b/webapp/packages/core-blocks/src/FormControls/Combobox.tsx index 33d16c8f14..02f636bca3 100644 --- a/webapp/packages/core-blocks/src/FormControls/Combobox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Combobox.tsx @@ -82,7 +82,7 @@ const styles = css` outline: none; color: inherit; cursor: pointer; - gap: 8px; + gap: 16px; & item-icon, & item-title { @@ -193,7 +193,7 @@ export const Combobox: ComboboxType = observer(function Combobox({ iconSelector, titleSelector, isDisabled, - onChange = () => { }, + onChange = () => {}, onSelect, onSwitch, ...rest diff --git a/webapp/packages/core-blocks/src/HTMLTemplates/sso.html.ejs b/webapp/packages/core-blocks/src/HTMLTemplates/sso.html.ejs index e16b59246d..bdf9242b11 100644 --- a/webapp/packages/core-blocks/src/HTMLTemplates/sso.html.ejs +++ b/webapp/packages/core-blocks/src/HTMLTemplates/sso.html.ejs @@ -13,7 +13,7 @@ header { flex-grow: 0; flex-shrink: 0; - height: 48px; + height: 32px; padding: 0 16px; background-color: #2a7cb4; display: flex; @@ -21,8 +21,8 @@ } .logo { - height: 34px; - width: 154px; + height: 24px; + width: 120px; } .content { @@ -45,16 +45,12 @@
-

- Authorization was successful. -

-

- Now you can close this page. -

+

Authorization was successful.

+

Now you can close this page.

\ No newline at end of file + diff --git a/webapp/packages/core-blocks/src/InfoItem.tsx b/webapp/packages/core-blocks/src/InfoItem.tsx index e4f31ec551..b09d53b91a 100644 --- a/webapp/packages/core-blocks/src/InfoItem.tsx +++ b/webapp/packages/core-blocks/src/InfoItem.tsx @@ -31,7 +31,7 @@ const styles = css` IconOrImage { width: 24px; height: 24px; - margin-right: 16px; + margin-right: 8px; } `; diff --git a/webapp/packages/core-blocks/src/ItemList/styles.ts b/webapp/packages/core-blocks/src/ItemList/styles.ts index 8a1ff690e3..37960b706a 100644 --- a/webapp/packages/core-blocks/src/ItemList/styles.ts +++ b/webapp/packages/core-blocks/src/ItemList/styles.ts @@ -39,7 +39,7 @@ export const ITEM_LIST_STYLES = css` composes: theme-background-surface theme-text-on-surface theme-typography--body1 from global; position: sticky; top: 0; - padding: 16px 24px; + padding: 8px 16px; z-index: 1; & input-box { @@ -83,13 +83,13 @@ export const ITEM_LIST_STYLES = css` display: flex; box-sizing: border-box; align-items: center; - padding: 0 16px; + padding: 0 8px; } list-item-icon { display: flex; align-items: center; box-sizing: border-box; - padding: 8px; + padding: 4px; & StaticImage { box-sizing: border-box; @@ -101,7 +101,7 @@ export const ITEM_LIST_STYLES = css` box-sizing: border-box; font-weight: 500; min-width: 250px; - padding: 8px 24px 8px 8px; + padding: 4px 16px 4px 8px; border-right: 1px solid; white-space: nowrap; @@ -112,7 +112,7 @@ export const ITEM_LIST_STYLES = css` composes: theme-typography--body2 from global; box-sizing: border-box; max-width: 460px; - padding: 8px 8px 8px 24px; + padding: 4px 8px 4px 16px; white-space: nowrap; overflow: hidden; diff --git a/webapp/packages/core-blocks/src/Menu/Menu.m.css b/webapp/packages/core-blocks/src/Menu/Menu.m.css new file mode 100644 index 0000000000..851b929b69 --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/Menu.m.css @@ -0,0 +1,28 @@ +.menuButton { + composes: theme-ripple from global; +} +.menuButton { + background: none; + border: none; + outline: none !important; + color: inherit; + cursor: pointer; + + & .box { + display: flex; + align-items: center; + flex: 1; + height: inherit; + } +} +.menuButtonLink { + position: absolute; + width: 0; + height: 0; + overflow: hidden; + box-sizing: border-box; + padding: 0; + margin: 0; + border: none; + background: transparent; +} diff --git a/webapp/packages/core-blocks/src/Menu/Menu.tsx b/webapp/packages/core-blocks/src/Menu/Menu.tsx index eb4f09a45f..dd2e12855c 100644 --- a/webapp/packages/core-blocks/src/Menu/Menu.tsx +++ b/webapp/packages/core-blocks/src/Menu/Menu.tsx @@ -8,25 +8,22 @@ import { observer } from 'mobx-react-lite'; import React, { forwardRef, useLayoutEffect, useRef, useState } from 'react'; import { MenuButton, MenuInitialState, useMenuState } from 'reakit/Menu'; -import styled from 'reshadow'; - -import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { ErrorBoundary } from '../ErrorBoundary'; +import { s } from '../s'; import { useCombinedRef } from '../useCombinedRef'; import { useObjectRef } from '../useObjectRef'; -import { useStyles } from '../useStyles'; +import { useS } from '../useS'; +import style from './Menu.m.css'; import { MenuPanel } from './MenuPanel'; -import { menuPanelStyles } from './menuPanelStyles'; import { IMenuState, MenuStateContext } from './MenuStateContext'; import type { IMouseContextMenu } from './useMouseContextMenu'; -interface IMenuProps extends Omit, 'style'> { +interface IMenuProps extends React.ButtonHTMLAttributes { mouseContextMenu?: IMouseContextMenu; label: string; items: React.ReactNode | (() => React.ReactNode); menuRef?: React.RefObject; - style?: ComponentStyle; disclosure?: boolean; placement?: MenuInitialState['placement']; submenu?: boolean; @@ -48,7 +45,6 @@ export const Menu = observer( menuRef, disclosure, children, - style, placement, visible, hasBindings, @@ -58,6 +54,7 @@ export const Menu = observer( modal, submenu, rtl, + className, ...props }, ref, @@ -74,7 +71,7 @@ export const Menu = observer( visible, rtl, }); - const styles = useStyles(menuPanelStyles, style); + const styles = useS(style); if (menuRef) { //@ts-expect-error Ref mutation @@ -130,17 +127,24 @@ export const Menu = observer( const MenuButtonLink = MenuButton; if (React.isValidElement(children) && disclosure) { - return styled(styles)( + return ( - + {disclosureProps => React.cloneElement(children, { ...disclosureProps, ...children.props })} ( > {items} - {relativePosition && } + {relativePosition && ( + + )} - , + ); } - return styled(styles)( + return ( - - {children} + +
{children}
( > {items} - {relativePosition && } + {relativePosition && ( + + )}
-
, + ); }), ); diff --git a/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx b/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx index f2a825498d..8607665370 100644 --- a/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx +++ b/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx @@ -6,29 +6,19 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled from 'reshadow'; - -import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { useTranslate } from '../localization/useTranslate'; -import { useStyles } from '../useStyles'; import { MenuItem } from './MenuItem'; import { MenuItemElement } from './MenuItemElement'; -import { menuPanelStyles } from './menuPanelStyles'; -interface Props extends Omit, 'style'> { - style?: ComponentStyle; -} - -export const MenuEmptyItem = observer(function MenuEmptyItem({ style }) { - const styles = useStyles(menuPanelStyles, style); +export const MenuEmptyItem = observer(function MenuEmptyItem() { const translate = useTranslate(); const label = translate('core_ui_empty'); - return styled(styles)( - - - , + return ( + + + ); }); diff --git a/webapp/packages/core-blocks/src/Menu/MenuItem.m.css b/webapp/packages/core-blocks/src/Menu/MenuItem.m.css new file mode 100644 index 0000000000..0b74547cfc --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuItem.m.css @@ -0,0 +1,23 @@ +.menuItem { + composes: theme-ripple from global; + composes: menuItem from './MenuItemElement.m.css'; + + display: flex; + align-items: center; + border: none; + padding: 0; + background: none; + text-align: left; + outline: none; + color: inherit; + cursor: pointer; + white-space: nowrap; + + &.hidden { + display: none; + } + + &:global([aria-selected='true']) { + opacity: 0.7; + } +} diff --git a/webapp/packages/core-blocks/src/Menu/MenuItem.tsx b/webapp/packages/core-blocks/src/Menu/MenuItem.tsx index cc894dde3d..c7fcfa0678 100644 --- a/webapp/packages/core-blocks/src/Menu/MenuItem.tsx +++ b/webapp/packages/core-blocks/src/Menu/MenuItem.tsx @@ -8,46 +8,44 @@ import { observer } from 'mobx-react-lite'; import React, { useContext } from 'react'; import { MenuItem as ReakitMenuItem } from 'reakit/Menu'; -import styled, { use } from 'reshadow'; - -import type { ComponentStyle } from '@cloudbeaver/core-theming'; +import type { CompositeItemOptions } from 'reakit/ts'; +import { s } from '../s'; import { useCombinedHandler } from '../useCombinedHandler'; -import { useStyles } from '../useStyles'; -import { menuPanelStyles } from './menuPanelStyles'; +import { useS } from '../useS'; +import style from './MenuItem.m.css'; import { MenuStateContext } from './MenuStateContext'; +import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent'; -export interface IMenuItemProps extends Omit, 'style'> { - label: string; - hidden?: boolean; +export type MenuItemOptions = CompositeItemOptions & { selected?: boolean; close?: boolean; - style?: ComponentStyle; -} +}; -export const MenuItem = observer(function MenuItem({ label, children, hidden, selected, close, style, onClick, ...rest }) { - const menu = useContext(MenuStateContext); - const styles = useStyles(menuPanelStyles, style); +export const MenuItem: ReakitProxyComponent<'button', MenuItemOptions> = observer>( + function MenuItem({ children, hidden, selected, close, onClick, className, ...rest }) { + const menu = useContext(MenuStateContext); + const styles = useS(style); - const handleClick = useCombinedHandler<[React.MouseEvent]>(onClick, function handleClick() { - if (close) { - menu?.hide(); - } - }); + const handleClick = useCombinedHandler<[React.MouseEvent]>(onClick, function handleClick() { + if (close) { + menu?.hide(); + } + }); - const MenuItem = ReakitMenuItem; + const MenuItem = ReakitMenuItem; - return styled(styles)( - , - ); -}); + return ( + + ); + }, +); diff --git a/webapp/packages/core-blocks/src/Menu/MenuItemCheckbox.tsx b/webapp/packages/core-blocks/src/Menu/MenuItemCheckbox.tsx new file mode 100644 index 0000000000..74cdd57dd1 --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuItemCheckbox.tsx @@ -0,0 +1,34 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; +import { MenuItemOptions, MenuItemCheckbox as ReakitMenuItemCheckbox } from 'reakit/Menu'; +import type { CheckboxOptions } from 'reakit/ts'; + +import { s } from '../s'; +import { useS } from '../useS'; +import style from './MenuItem.m.css'; +import { MenuStateContext } from './MenuStateContext'; +import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent'; + +type Options = CheckboxOptions & MenuItemOptions; + +export const MenuItemCheckbox: ReakitProxyComponent<'button', Options> = observer>( + function MenuItemCheckbox({ hidden, children, className, ...rest }) { + const menu = useContext(MenuStateContext); + const styles = useS(style); + + const MenuItemCheckbox = ReakitMenuItemCheckbox; + + return ( + + ); + }, +); diff --git a/webapp/packages/core-blocks/src/Menu/MenuItemElement.m.css b/webapp/packages/core-blocks/src/Menu/MenuItemElement.m.css new file mode 100644 index 0000000000..e7bf19eb1e --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuItemElement.m.css @@ -0,0 +1,70 @@ +.menuPanelItem { + composes: theme-ripple theme-border-color-background from global; + + flex: 1; + display: flex; + position: relative; + align-items: center; + height: 26px; + padding: 0 4px; + + & .menuItemText, + & .menuItemBinding { + display: block; + padding: 0 4px; + } + & .menuItemBinding { + flex: 1; + text-align: right; + padding-left: 32px; + width: 80px; + overflow: hidden; + text-overflow: ellipsis; + } + & .menuItemIcon, + & .menuItemContent { + width: 24px; + height: 24px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + } + & .menuItemContent .icon { + width: 10px; + height: 10px; + opacity: 0.5; + } + & .iconOrImage { + width: 16px; + height: 16px; + object-fit: contain; + } + & .loader { + width: 16px; + } +} + +.menuItemBinding { + composes: theme-typography--caption from global; +} + +.menuBox:not([data-s-has-bindings='true']) .menuItemBinding { + width: 0; +} + +.menuBox[dir='rtl'] { + & .menuItemText { + text-align: right; + } + & .menuItemContent .icon { + transform: rotate(180deg); + } +} + +.menuItem:hover, +.menuItem:global([aria-expanded='true']) { + & .menuItemContent .icon { + opacity: 1; + } +} diff --git a/webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx b/webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx index d64a384e7f..2f9ca942e8 100644 --- a/webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx +++ b/webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx @@ -6,17 +6,15 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled from 'reshadow'; - -import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { Icon } from '../Icon'; import { IconOrImage } from '../IconOrImage'; import { Loader } from '../Loader/Loader'; import { useTranslate } from '../localization/useTranslate'; +import { s } from '../s'; +import { useS } from '../useS'; import { useStateDelay } from '../useStateDelay'; -import { useStyles } from '../useStyles'; -import { menuPanelStyles } from './menuPanelStyles'; +import style from './MenuItemElement.m.css'; interface IMenuItemElementProps { label: string; @@ -28,7 +26,6 @@ interface IMenuItemElementProps { menu?: boolean; loading?: boolean; panelAvailable?: boolean; - style?: ComponentStyle; } export const MenuItemElement = observer(function MenuItemElement({ @@ -40,22 +37,34 @@ export const MenuItemElement = observer(function MenuItem menu, panelAvailable, loading = false, - style = [], }) { + const styles = useS(style); const translate = useTranslate(); const title = translate(label); loading = useStateDelay(loading, 100); - return styled(useStyles(menuPanelStyles, style))( - - {typeof icon === 'string' ? : icon} - {displayLabel ? {title} : } - {binding} - - {loading && } - {panelAvailable !== false && menu && !loading && } - - , + return ( +
+
+ + {typeof icon === 'string' ? : icon} + +
+ {displayLabel ? ( +
+ {title} +
+ ) : ( +
+ )} +
+ {binding} +
+
+ {loading && } + {panelAvailable !== false && menu && !loading && } +
+
); }); diff --git a/webapp/packages/core-blocks/src/Menu/MenuItemRadio.tsx b/webapp/packages/core-blocks/src/Menu/MenuItemRadio.tsx new file mode 100644 index 0000000000..4109d6af2e --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuItemRadio.tsx @@ -0,0 +1,34 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; +import { MenuItemOptions, MenuItemRadio as ReakitMenuItemRadio } from 'reakit/Menu'; +import type { RadioOptions } from 'reakit/ts'; + +import { s } from '../s'; +import { useS } from '../useS'; +import style from './MenuItem.m.css'; +import { MenuStateContext } from './MenuStateContext'; +import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent'; + +type Options = RadioOptions & MenuItemOptions; + +export const MenuItemRadio: ReakitProxyComponent<'button', Options> = observer>( + function MenuItemRadio({ hidden, children, className, ...rest }) { + const menu = useContext(MenuStateContext); + const styles = useS(style); + + const MenuItemRadio = ReakitMenuItemRadio; + + return ( + + ); + }, +); diff --git a/webapp/packages/core-blocks/src/Menu/MenuPanel.m.css b/webapp/packages/core-blocks/src/Menu/MenuPanel.m.css new file mode 100644 index 0000000000..de1841b4fe --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuPanel.m.css @@ -0,0 +1,27 @@ +.menu { + outline: none; + z-index: 999; +} +.menuBox { + composes: theme-background-surface theme-text-on-surface theme-group-element-radius theme-typography--caption theme-elevation-z5 from global; + composes: menuBox from './MenuItemElement.m.css'; + + display: flex; + flex-direction: column; + min-width: 140px; + padding: 6px 0; + + & .menuBox { + margin-top: -6px; + } +} + +.menu.modal .menuBox { + overflow: auto; + max-height: 80vh; + overflow-x: hidden; +} + +.menu.submenu { + margin-top: -6px; +} diff --git a/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx b/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx index a58f87c6a1..245d35b7cc 100644 --- a/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx +++ b/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx @@ -8,15 +8,13 @@ import { observer } from 'mobx-react-lite'; import { Children, forwardRef } from 'react'; import { Menu, MenuStateReturn } from 'reakit/Menu'; -import styled, { use } from 'reshadow'; - -import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { ErrorBoundary } from '../ErrorBoundary'; import { getComputed } from '../getComputed'; -import { useStyles } from '../useStyles'; +import { s } from '../s'; +import { useS } from '../useS'; import { MenuEmptyItem } from './MenuEmptyItem'; -import { menuPanelStyles } from './menuPanelStyles'; +import style from './MenuPanel.m.css'; export interface IMenuPanelProps { label: string; @@ -27,13 +25,12 @@ export interface IMenuPanelProps { children: React.ReactNode | (() => React.ReactNode); rtl?: boolean; submenu?: boolean; - style?: ComponentStyle; className?: string; } export const MenuPanel = observer( - forwardRef(function MenuPanel({ label, menu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, style, className }, ref) { - const styles = useStyles(menuPanelStyles, style); + forwardRef(function MenuPanel({ label, menu, submenu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, className }, ref) { + const styles = useS(style); const visible = menu.visible; if (!visible) { @@ -48,15 +45,21 @@ export const MenuPanel = observer( renderedChildren = typeof children === 'function' ? children() : children; } - return styled(styles)( + return ( - - - {Children.count(renderedChildren) === 0 && } + +
+ {Children.count(renderedChildren) === 0 && } {renderedChildren} - +
- , + ); }), ); diff --git a/webapp/packages/core-blocks/src/Menu/MenuSeparator.m.css b/webapp/packages/core-blocks/src/Menu/MenuSeparator.m.css new file mode 100644 index 0000000000..a7ceb5aa90 --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuSeparator.m.css @@ -0,0 +1,11 @@ +.menuSeparator { + flex: 1; + width: 100%; + display: flex; + margin: 4px 0; + + &:first-child, + &:last-child { + display: none; + } +} diff --git a/webapp/packages/core-blocks/src/Menu/MenuSeparator.tsx b/webapp/packages/core-blocks/src/Menu/MenuSeparator.tsx new file mode 100644 index 0000000000..0af23f447a --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/MenuSeparator.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 { MenuSeparator as MenuSeparatorBase } from 'reakit/Menu'; +import type { SeparatorOptions } from 'reakit/ts'; + +import { s } from '../s'; +import { useS } from '../useS'; +import style from './MenuSeparator.m.css'; +import type { ReakitProxyComponentOptions } from './ReakitProxyComponent'; + +export const MenuSeparator = function MenuSeparator({ className, ...props }: ReakitProxyComponentOptions<'hr', SeparatorOptions>) { + const styles = useS(style); + return ; +}; diff --git a/webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts b/webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts new file mode 100644 index 0000000000..6f5c507b9f --- /dev/null +++ b/webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts @@ -0,0 +1,25 @@ +/* + * 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 type { ReactElement } from 'react'; +import type { As, PropsWithAs } from 'reakit-utils/types'; + +export type ReakitProxyPropsWithAs = Omit, 'children'> & { + children?: React.ReactNode; +}; + +export type ReakitProxyComponentOptions = ReakitProxyPropsWithAs; + +export type ReakitProxyComponent = { + ( + props: ReakitProxyPropsWithAs & { + as: TT; + }, + context?: any, + ): ReactElement | null; + (props: ReakitProxyPropsWithAs, context?: any): ReactElement | null; +}; diff --git a/webapp/packages/core-blocks/src/Menu/menuPanelStyles.ts b/webapp/packages/core-blocks/src/Menu/menuPanelStyles.ts deleted file mode 100644 index 137cd3d6c6..0000000000 --- a/webapp/packages/core-blocks/src/Menu/menuPanelStyles.ts +++ /dev/null @@ -1,186 +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 menuPanelStyles = css` - menu-box { - composes: theme-background-surface theme-text-on-surface from global; - } - MenuItem, - MenuItemCheckbox, - MenuItemRadio, - MenuButton, - MenuItemElement { - composes: theme-ripple from global; - } - menu-panel-item, - MenuSeparator { - composes: theme-border-color-background from global; - } - MenuButton { - background: none; - border: none; - outline: none !important; - color: inherit; - cursor: pointer; - & box { - display: flex; - align-items: center; - flex: 1; - height: inherit; - } - } - MenuButtonLink { - position: absolute; - width: 0; - height: 0; - overflow: hidden; - box-sizing: border-box; - padding: 0; - margin: 0; - border: none; - background: transparent; - } - Menu { - outline: none; - z-index: 999; - } - MenuPanel[submenu] { - margin-top: -6px; - } - menu-box { - composes: theme-group-element-radius theme-typography--body2 theme-elevation-z5 from global; - min-width: 140px; - padding: 6px 0; - - & menu-box { - margin-top: -6px; - } - } - - menu-box { - display: flex; - flex-direction: column; - - &:not([|hasBindings]) menu-item-binding { - width: 0; - } - } - - Menu[modal] menu-box { - overflow: auto; - max-height: 80vh; - overflow-x: hidden; - } - - MenuItem, - MenuItemCheckbox, - MenuItemRadio { - display: flex; - align-items: center; - border: none; - padding: 0; - background: none; - text-align: left; - outline: none; - color: inherit; - cursor: pointer; - white-space: nowrap; - - &[use|hidden] { - display: none; - } - - &:global([aria-selected='true']) { - opacity: 0.7; - } - - &:hover, - &:global([aria-expanded='true']) { - & Icon { - opacity: 1; - } - } - } - - MenuSeparator { - flex: 1; - width: 100%; - display: flex; - margin: 6px 0; - border: none !important; - border-bottom: 1px solid !important; - - &:first-child, - &:last-child { - display: none; - } - } - - menu-item-binding { - composes: theme-typography--caption from global; - } - - menu-panel-item { - flex: 1; - display: flex; - position: relative; - align-items: center; - height: 30px; - padding: 0 4px; - - & menu-item-text, - & menu-item-binding { - display: block; - padding: 0 4px; - } - & menu-item-binding { - flex: 1; - text-align: right; - padding-left: 32px; - width: 80px; - overflow: hidden; - text-overflow: ellipsis; - } - & menu-item-icon, - & menu-item-content { - width: 24px; - height: 24px; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - } - & menu-item-content Icon { - width: 10px; - height: 10px; - } - & Icon { - width: 16px; - height: 16px; - opacity: 0.5; - } - & IconOrImage { - width: 16px; - height: 16px; - object-fit: contain; - } - & Loader { - width: 16px; - } - } - - Menu[rtl] { - & menu-item-text { - text-align: right; - } - & menu-panel-item menu-item-content Icon { - transform: rotate(180deg); - } - } -`; diff --git a/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts b/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts index 46317c1020..bdd375733f 100644 --- a/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts +++ b/webapp/packages/core-blocks/src/Overlay/OVERLAY_BASE_STYLES.ts @@ -60,7 +60,7 @@ export const OVERLAY_BASE_STYLES = css` message, actions { flex-shrink: 0; - padding: 24px; + padding: 16px; } message { diff --git a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx index a1e45cc736..70ad8f9f2e 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx +++ b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx @@ -37,7 +37,7 @@ const styles = css` align-items: center; box-sizing: border-box; flex: 1; - padding: 4px 0; + padding: 2px 0; & ShadowInput { height: 24px; diff --git a/webapp/packages/core-blocks/src/SContext.tsx b/webapp/packages/core-blocks/src/SContext.tsx new file mode 100644 index 0000000000..a0ca3d8437 --- /dev/null +++ b/webapp/packages/core-blocks/src/SContext.tsx @@ -0,0 +1,31 @@ +/* + * 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 React, { createContext } from 'react'; + +import type { IStyleRegistry, Style } from '@cloudbeaver/core-theming'; + +type Registry = Map; + +export type StyleRegistry = [Style, IStyleRegistry][]; + +export const SContextReact = createContext(new Map()); + +type Props = { + registry: StyleRegistry; +}; + +export const SContext: React.FC> = function SContext({ registry, children }) { + const context = React.useContext(SContextReact); + const state = new Map(context.entries()); + + for (const [style, styles] of registry) { + state.set(style, [...(state.get(style) || []), styles]); + } + + return {children}; +}; diff --git a/webapp/packages/core-blocks/src/Split/SplitControls.m.css b/webapp/packages/core-blocks/src/Split/SplitControls.m.css new file mode 100644 index 0000000000..2f2c587725 --- /dev/null +++ b/webapp/packages/core-blocks/src/Split/SplitControls.m.css @@ -0,0 +1,123 @@ +.container { + position: absolute; + display: flex; + justify-content: center; + align-content: center; + align-items: center; + + /* pointer outline */ + &::before { + content: ''; + position: absolute; + background: transparent; + } +} + +.button { + composes: theme-button theme-button_unelevated theme-button_background theme-elevation-z3 from global; + opacity: 0; + transition-property: opacity, box-shadow !important; + transition: opacity 0.3s ease-in-out; +} + +.ripple { + composes: theme-button_ripple from global; +} + +.container:hover .button { + opacity: 1; +} + +.container[data-s-split='vertical'] { + flex-direction: column; + cursor: ew-resize; + height: 100%; + width: 5px; + left: -2px; + + &[data-s-mode='minimize'] { + left: 0; + } + + &[data-s-mode='maximize'] { + left: -2px; + } + + & .button { + margin: 8px 0; + min-width: 16px; + + &::after { + transform: rotate(180deg); + } + } + + &[data-s-mode='maximize'] .button, + &[data-s-mode='minimize'] .button { + min-height: 64px; + } + + & .button.primary::after { + transform: rotate(0deg); + } +} + +.container[data-s-split='vertical'].inverse { + flex-direction: column-reverse; +} + +.container[data-s-split='horizontal'] { + flex-direction: row; + cursor: ns-resize; + width: 100%; + height: 5px; + top: -2px; + + &[data-s-mode='minimize'] { + top: 0; + } + + &[data-s-mode='maximize'] { + top: -2px; + } + + & .button { + margin: 0 8px; + min-width: 28px; + height: 16px; + + &::after { + transform: rotate(270deg); + } + } + + &[data-s-mode='maximize'] .button, + &[data-s-mode='minimize'] .button { + min-width: 64px; + } + + & .button.primary::after { + transform: rotate(90deg); + } +} + +.container[data-s-split='horizontal'].inverse { + flex-direction: row-reverse; +} + +.button { + padding: 0 !important; + &::after { + position: absolute; + top: 50%; + left: 50%; + content: ''; + display: block; + width: 0; + height: 0; + border-style: solid; + border-width: 3px 4px 3px 0; + border-color: transparent #000 transparent transparent; + margin: -3px -2px; + } +} diff --git a/webapp/packages/core-blocks/src/Split/SplitControls.tsx b/webapp/packages/core-blocks/src/Split/SplitControls.tsx index f60063a3c0..a489ab322a 100644 --- a/webapp/packages/core-blocks/src/Split/SplitControls.tsx +++ b/webapp/packages/core-blocks/src/Split/SplitControls.tsx @@ -5,14 +5,15 @@ * 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 { s } from '../s'; import { useObjectRef } from '../useObjectRef'; -import { buttonStyles } from './splitButtonStyles'; +import { useS } from '../useS'; +import SplitControlsStyles from './SplitControls.m.css'; import { useSplit } from './useSplit'; export const SplitControls: React.FC = function SplitControls() { const split = useSplit(); + const styles = useS(SplitControlsStyles); const inverse = split.state.isMainSecond(); @@ -51,25 +52,27 @@ export const SplitControls: React.FC = function SplitControls() { ['handleCollapse', 'handleExpand'], ); - return styled(buttonStyles)( - {split.state.mode !== 'minimize' && ( - )} {split.state.mode !== 'maximize' && ( - )} - , +
); }; diff --git a/webapp/packages/core-blocks/src/Split/splitButtonStyles.ts b/webapp/packages/core-blocks/src/Split/splitButtonStyles.ts deleted file mode 100644 index 2c23461da5..0000000000 --- a/webapp/packages/core-blocks/src/Split/splitButtonStyles.ts +++ /dev/null @@ -1,131 +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 buttonStyles = css` - container { - position: absolute; - display: flex; - justify-content: center; - align-content: center; - align-items: center; - - /* pointer outline */ - &::before { - content: ''; - position: absolute; - background: transparent; - } - } - - button { - composes: theme-button theme-button_unelevated theme-button_background theme-elevation-z3 from global; - opacity: 0; - transition-property: opacity, box-shadow !important; - transition: opacity 0.3s ease-in-out; - } - - ripple { - composes: theme-button_ripple from global; - } - - container:hover button { - opacity: 1; - } - - container[|split='vertical'] { - flex-direction: column; - cursor: ew-resize; - height: 100%; - width: 5px; - left: -2px; - - &[|mode='minimize'] { - left: 0; - } - - &[|mode='maximize'] { - left: -2px; - } - - & button { - margin: 8px 0; - min-width: 16px; - - &::after { - transform: rotate(180deg); - } - } - - &[|mode='maximize'] button, - &[|mode='minimize'] button { - min-height: 64px; - } - - & button[|isPrimary]::after { - transform: rotate(0deg); - } - } - container[|split='vertical'][|inverse] { - flex-direction: column-reverse; - } - - container[|split='horizontal'] { - flex-direction: row; - cursor: ns-resize; - width: 100%; - height: 5px; - top: -2px; - - &[|mode='minimize'] { - top: 0; - } - - &[|mode='maximize'] { - top: -2px; - } - - & button { - margin: 0 8px; - min-width: 28px; - height: 16px; - - &::after { - transform: rotate(270deg); - } - } - - &[|mode='maximize'] button, - &[|mode='minimize'] button { - min-width: 64px; - } - - & button[|isPrimary]::after { - transform: rotate(90deg); - } - } - container[|split='horizontal'][|inverse] { - flex-direction: row-reverse; - } - button { - padding: 0 !important; - &::after { - position: absolute; - top: 50%; - left: 50%; - content: ''; - display: block; - width: 0; - height: 0; - border-style: solid; - border-width: 3px 4px 3px 0; - border-color: transparent #000 transparent transparent; - margin: -3px -2px; - } - } -`; diff --git a/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts b/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts index dbb24634d9..ad6e0d6670 100644 --- a/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts +++ b/webapp/packages/core-blocks/src/Table/BASE_TABLE_STYLES.ts @@ -35,15 +35,15 @@ export const BASE_TABLE_STYLES = css` &[|size='big'] { & > thead > tr > th { - height: 54px; + height: 36px; &[|min] { - width: 46px; + width: 32px; } } & > tbody > tr > td { - height: 46px; + height: 32px; } } @@ -83,7 +83,7 @@ export const BASE_TABLE_STYLES = css` th { box-sizing: border-box; white-space: nowrap; - padding: 16px; + padding: 8px; height: 36px; padding-top: unset; padding-bottom: unset; @@ -114,7 +114,7 @@ export const BASE_TABLE_STYLES = css` position: relative; box-sizing: border-box; height: 28px; - padding: 0 16px; + padding: 0 8px; transition: padding ease-in-out 0.24s; &[|ellipsis] { diff --git a/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx b/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx index 56287d3465..f7cd8f9c44 100644 --- a/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx +++ b/webapp/packages/core-blocks/src/Table/TableItemSelect.tsx @@ -26,8 +26,8 @@ interface Props { const checkboxStyles = css` Checkbox { - margin-left: -10px; - margin-right: -10px; + margin-left: -6px; + margin-right: -6px; } `; diff --git a/webapp/packages/core-blocks/src/Table/TableSelect.tsx b/webapp/packages/core-blocks/src/Table/TableSelect.tsx index 6bc441f8e0..82f882097e 100644 --- a/webapp/packages/core-blocks/src/Table/TableSelect.tsx +++ b/webapp/packages/core-blocks/src/Table/TableSelect.tsx @@ -23,8 +23,8 @@ interface Props { const styles = css` Checkbox { - margin-left: -10px; - margin-right: -10px; + margin-left: -6px; + margin-right: -6px; } `; diff --git a/webapp/packages/core-blocks/src/Tags/Tags.tsx b/webapp/packages/core-blocks/src/Tags/Tags.tsx index b4a6203b7b..667fdfc240 100644 --- a/webapp/packages/core-blocks/src/Tags/Tags.tsx +++ b/webapp/packages/core-blocks/src/Tags/Tags.tsx @@ -20,7 +20,7 @@ const style = css` width: 100%; display: flex; flex-wrap: wrap; - gap: 8px; + gap: 16px; } `; diff --git a/webapp/packages/core-blocks/src/ToolsPanel/ToolsPanel.tsx b/webapp/packages/core-blocks/src/ToolsPanel/ToolsPanel.tsx index 6b7a3bf748..32fa9e9bc0 100644 --- a/webapp/packages/core-blocks/src/ToolsPanel/ToolsPanel.tsx +++ b/webapp/packages/core-blocks/src/ToolsPanel/ToolsPanel.tsx @@ -9,7 +9,7 @@ import styled, { css } from 'reshadow'; const styles = css` container { - height: 32px; + height: 30px; display: flex; flex-shrink: 0; } diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeStyles.ts b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeStyles.ts index 47e19c08d1..248367e3e0 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeStyles.ts +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeStyles.ts @@ -59,6 +59,10 @@ export const TREE_NODE_STYLES = css` margin-right: 0; margin-left: 4px; } + + & TreeNodeName { + margin-right: 4px; + } } } @@ -95,6 +99,7 @@ export const TREE_NODE_STYLES = css` padding-right: 0; display: flex; align-items: center; + margin-right: 4px; } TreeNodeFilter { @@ -142,21 +147,18 @@ export const TREE_NODE_STYLES = css` } node TreeNodeControl[big] { - height: 46px; - padding: 2 16px; + height: 32px; + padding: 0 8px; - & TreeNodeSelect { - margin: 3px; - } & TreeNodeName { margin: 0; - padding-right: 16px; - padding-left: 16px; + padding-right: 4px; + padding-left: 4px; } & TreeNodeIcon, & TreeNodeExpand { - width: 24px; - height: 24px; + width: 16px; + height: 16px; } & TreeNodeExpand [|size='small'] { @@ -168,12 +170,16 @@ export const TREE_NODE_STYLES = css` } & > * { - margin-right: 11px; - margin-left: 11px; + margin-right: 4px; + margin-left: 4px; + } + + & TreeNodeSelect { + margin: 0; } } node TreeNodeControl[big] + TreeNodeNested { - padding-left: 46px; + padding-left: 16px; } `; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 371165db83..ad7a892281 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -23,15 +23,23 @@ export * from './localization/Translate'; export * from './localization/useTranslate'; export * from './ConnectionImageWithMask/ConnectionImageWithMask'; +export { default as ConnectionImageWithMaskSvgStyles } from './ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css'; export * from './Menu/ACTION_ICON_BUTTON_STYLES'; export * from './Menu/Menu'; +export { default as MenuStyles } from './Menu/Menu.m.css'; export * from './Menu/MenuBarSmallItem'; export * from './Menu/MenuEmptyItem'; export * from './Menu/MenuItem'; +export { default as MenuItemStyles } from './Menu/MenuItem.m.css'; +export * from './Menu/MenuItemCheckbox'; export * from './Menu/MenuItemElement'; +export { default as MenuItemElementStyles } from './Menu/MenuItemElement.m.css'; +export * from './Menu/MenuItemRadio'; export * from './Menu/MenuPanel'; -export * from './Menu/menuPanelStyles'; +export { default as MenuPanelStyles } from './Menu/MenuPanel.m.css'; +export * from './Menu/MenuSeparator'; +export { default as MenuSeparatorStyles } from './Menu/MenuSeparator.m.css'; export * from './Menu/MenuStateContext'; export * from './Menu/useMouseContextMenu'; @@ -156,6 +164,8 @@ export * from './ExceptionMessage'; export * from './getComputed'; export * from './IconButton'; export * from './IconOrImage'; +export * from './s'; +export * from './SContext'; export * from './StaticImage'; export * from './TextPlaceholder'; export * from './TimerIcon'; @@ -177,6 +187,7 @@ export * from './useObjectRef'; export * from './useObservableRef'; export * from './usePermission'; export * from './usePromiseState'; +export * from './useS'; export * from './useStateDelay'; export * from './useErrorDetails'; export * from './useActivationDelay'; diff --git a/webapp/packages/core-blocks/src/layout/AppLogo.tsx b/webapp/packages/core-blocks/src/layout/AppLogo.tsx index 693c43180a..2ac4834d1e 100644 --- a/webapp/packages/core-blocks/src/layout/AppLogo.tsx +++ b/webapp/packages/core-blocks/src/layout/AppLogo.tsx @@ -14,13 +14,13 @@ const styles = css` height: 100%; display: flex; align-items: center; - padding: 0 14px; + padding: 0 8px; cursor: pointer; } IconOrImage { - height: 32px; - width: 154px; + height: 24px; + width: 120px; &[|small] { display: none; } diff --git a/webapp/packages/core-blocks/src/layout/TopAppBar.tsx b/webapp/packages/core-blocks/src/layout/TopAppBar.tsx index 9bc131a8ea..7d669d9194 100644 --- a/webapp/packages/core-blocks/src/layout/TopAppBar.tsx +++ b/webapp/packages/core-blocks/src/layout/TopAppBar.tsx @@ -12,7 +12,7 @@ export const topAppBarStyles = css` composes: theme-text-on-primary theme-typography--body2 from global; display: flex; align-items: center; - height: 48px; + height: 32px; padding: 0 8px; background-color: #2a7cb4; z-index: 1; diff --git a/webapp/packages/core-blocks/src/s.ts b/webapp/packages/core-blocks/src/s.ts new file mode 100644 index 0000000000..0f9471c711 --- /dev/null +++ b/webapp/packages/core-blocks/src/s.ts @@ -0,0 +1,12 @@ +/* + * 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 { type ClassValue, clsx } from '@cloudbeaver/core-utils'; + +export function s>(styles: T, options?: Partial>, ...inputs: ClassValue[]): string { + return clsx(...Object.entries(options ?? {}).map(([key, active]) => active && styles[key]), ...inputs); +} diff --git a/webapp/packages/core-blocks/src/useS.ts b/webapp/packages/core-blocks/src/useS.ts new file mode 100644 index 0000000000..94b044d739 --- /dev/null +++ b/webapp/packages/core-blocks/src/useS.ts @@ -0,0 +1,108 @@ +/* + * 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 { useContext, useMemo, useRef, useState } from 'react'; + +import { useService } from '@cloudbeaver/core-di'; +import { BaseStyles, ComponentStyle, Style, ThemeService } from '@cloudbeaver/core-theming'; + +import { SContextReact } from './SContext'; +import { useExecutor } from './useExecutor'; + +type Intersect = (T extends any ? (x: T) => 0 : never) extends (x: infer R) => 0 + ? R extends Record + ? BaseStyles + : never + : never; + +type ExtractStyles = Intersect< + T extends (infer U)[] + ? U extends BaseStyles + ? U + : U extends (theme: string) => Promise + ? A extends BaseStyles + ? A + : never + : never + : never +>; + +/** + * Changes styles depending on theme + * + * @param componentStyles styles array + */ +export function useS(...componentStyles: [...T]): ExtractStyles { + // todo do you understand that we store ALL STYLES in each component that uses this hook? + + const context = useContext(SContextReact); + const stylesRef = useRef([]); + const [patch, forceUpdate] = useState(0); + const loadedStyles = useRef([]); + const themeService = useService(ThemeService); + const [currentThemeId, setCurrentThemeId] = useState(() => themeService.currentThemeId); + const lastThemeRef = useRef(currentThemeId); + const filteredStyles = themeService.mapStyles(componentStyles.flat(Infinity).filter(Boolean) as Style[], context); + const trackTheme = filteredStyles.some(style => typeof style === 'function'); + + useExecutor({ + executor: themeService.onChange, + handlers: [ + function updateThemeId(theme) { + if (currentThemeId !== themeService.currentThemeId && trackTheme) { + setCurrentThemeId(theme.id); + } + }, + ], + }); + + 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]; + } + + 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); + + if (data instanceof Promise) { + themedStyles.push(data); + } else { + staticStyles.push(data); + } + } + loadedStyles.current = staticStyles.flat(Infinity); + + if (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]); + + return styles as ExtractStyles; // todo this method is called in each rerender +} + +export function combineStyles(styles: BaseStyles[]): BaseStyles { + const combined: BaseStyles = {}; + + for (const style of styles) { + for (const [key, value] of Object.entries(style)) { + combined[key] = [combined[key] || '', value].join(' ').trim(); + } + } + + return combined; +} diff --git a/webapp/packages/core-blocks/src/useStyles.ts b/webapp/packages/core-blocks/src/useStyles.ts index d32fafb864..78cd5dc26a 100644 --- a/webapp/packages/core-blocks/src/useStyles.ts +++ b/webapp/packages/core-blocks/src/useStyles.ts @@ -10,7 +10,6 @@ import { create } from 'reshadow'; import { useService } from '@cloudbeaver/core-di'; import { BaseStyles, ComponentStyle, Style, ThemeService } from '@cloudbeaver/core-theming'; -import { flat } from '@cloudbeaver/core-utils'; import { useExecutor } from './useExecutor'; @@ -28,7 +27,8 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record themeService.currentThemeId); const lastThemeRef = useRef(currentThemeId); - const filteredStyles = flat(componentStyles).filter(Boolean) as Style[]; + //@ts-ignore + const filteredStyles = componentStyles.flat(Infinity).filter(Boolean) as Style[]; const trackTheme = filteredStyles.some(style => typeof style === 'function'); useExecutor({ @@ -62,11 +62,11 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record 0) { Promise.all(themedStyles).then(styles => { - loadedStyles.current = flat([staticStyles, flat(styles)]).filter(Boolean) as BaseStyles[]; + loadedStyles.current = [staticStyles, styles].flat(Infinity).filter(Boolean) as BaseStyles[]; forceUpdate(patch + 1); }); } @@ -78,5 +78,6 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record { process.env.NODE_ENV = argv.mode; const devTool = 'source-map' in env && 'source-map'; @@ -49,7 +52,7 @@ module.exports = (env, argv) => { return loaders; } - function generateStyleLoaders(options = { hasModule: false, disable: false }) { + function generateStyleLoaders(options = { hasModule: false, disable: false, pure: false }) { const moduleScope = options.hasModule ? 'local' : 'global'; let modules = { mode: moduleScope, @@ -60,7 +63,11 @@ module.exports = (env, argv) => { modules = false; } - const postCssPlugins = [require('postcss-preset-env')({ stage: 0 }), require('@reshadow/postcss')({ scopeBehaviour: moduleScope })]; + const postCssPlugins = [require('postcss-preset-env')({ stage: 0 })]; + + if (!options.pure) { + postCssPlugins.push(require('@reshadow/postcss')({ scopeBehaviour: moduleScope })); + } return [ ...getBaseStyleLoaders(), @@ -68,26 +75,27 @@ module.exports = (env, argv) => { loader: 'css-loader', options: { modules: modules, - sourceMap: true, + sourceMap: devMode, }, }, - { loader: 'postcss-loader', options: { + sourceMap: devMode, postcssOptions: { plugins: postCssPlugins, - sourceMap: true, + sourceMap: devMode, }, }, }, { loader: 'sass-loader', options: { - sourceMap: true, + sourceMap: devMode, sassOptions: { implementation: require('sass'), includePaths: nodeModules, + quietDeps: true, }, }, }, @@ -97,7 +105,8 @@ module.exports = (env, argv) => { var babelLoader = { loader: require.resolve('babel-loader'), options: { - configFile: join(__dirname, 'babel.config.js'), + root: __dirname, + cacheDirectory: true, }, }; @@ -199,11 +208,16 @@ module.exports = (env, argv) => { }, { test: /\.(css|s[ac]ss)$/, + exclude: /node_modules/, oneOf: [ { test: /\.module\.(css|s[ac]ss)$/, use: generateStyleLoaders({ hasModule: true }), }, + { + test: /\.m\.(css|s[ac]ss)$/, + use: generateStyleLoaders({ hasModule: true, pure: true }), + }, { include: /node_modules/, use: [...getBaseStyleLoaders(), 'css-loader', 'sass-loader'], diff --git a/webapp/packages/core-cli/configs/webpack.product.dev.config.js b/webapp/packages/core-cli/configs/webpack.product.dev.config.js index f1e898a033..5746287eff 100644 --- a/webapp/packages/core-cli/configs/webpack.product.dev.config.js +++ b/webapp/packages/core-cli/configs/webpack.product.dev.config.js @@ -43,7 +43,8 @@ module.exports = (env, argv) => { sso, }, optimization: { - minimize: true, + minimize: false, + moduleIds: 'named', }, devServer: { allowedHosts: 'all', @@ -71,10 +72,7 @@ module.exports = (env, argv) => { httpProxy.createProxyServer({ target:`http://127.0.0.1:${port}` }).listen(8080); }, }, - devtool: 'eval-source-map', - // optimization: { - // moduleIds: 'named', - // }, + devtool: 'source-map', plugins: [ new CopyWebpackPlugin({ patterns: getAssets(package, ''), diff --git a/webapp/typings.d.ts b/webapp/packages/core-cli/global.d.ts similarity index 90% rename from webapp/typings.d.ts rename to webapp/packages/core-cli/global.d.ts index c895704bd6..b8bda413b0 100644 --- a/webapp/typings.d.ts +++ b/webapp/packages/core-cli/global.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -/// +/// declare module '*.scss?raw' { const classes: { readonly [key: string]: string }; @@ -16,6 +16,11 @@ declare module '*.scss' { export default classes; } +declare module '*.m.css' { + const classes: { [key: string]: string }; + export default classes; +} + declare module '*.css' { const classes: { readonly [key: string]: string }; export default classes; diff --git a/webapp/packages/core-cli/package.json b/webapp/packages/core-cli/package.json index 4af6e53f6b..b357005498 100644 --- a/webapp/packages/core-cli/package.json +++ b/webapp/packages/core-cli/package.json @@ -7,6 +7,7 @@ "files": [ "/package.json", "/tsconfig.json", + "/global.d.ts", "/src", "/configs", "/bin", @@ -71,8 +72,6 @@ }, "peerDependencies": { "@types/jest": "~29.x.x", - "@types/react": "~18.x.x", - "@types/react-dom": "~18.x.x", "babel-jest": "~29.x.x", "jest": "~29.x.x", "jest-environment-jsdom": "~29.x.x" diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts index 4a6f3cc023..bed5c92ba7 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts @@ -91,7 +91,7 @@ export const commonDialogBaseStyle = [ dialog-body, footer { flex-shrink: 0; - padding: 24px; + padding: 16px; &[|no-padding] { padding: 0px; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/styles.ts b/webapp/packages/core-dialogs/src/CommonDialog/styles.ts index 4e02b53727..57fdfaf383 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/styles.ts +++ b/webapp/packages/core-dialogs/src/CommonDialog/styles.ts @@ -26,7 +26,7 @@ export const dialogStyles = css` inner-box { display: flex; margin: auto; - padding: 24px; + padding: 16px; flex-direction: column; align-items: center; diff --git a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/styles.ts b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/styles.ts index 6c3815b8dd..7224f87b35 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/styles.ts +++ b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/styles.ts @@ -29,6 +29,6 @@ export const styles = css` CommonDialogFooter { align-items: center; justify-content: flex-end; - gap: 24px; + gap: 16px; } `; diff --git a/webapp/packages/core-theming/src/ComponentStyle.ts b/webapp/packages/core-theming/src/ComponentStyle.ts index 7b190dae06..bd2dee9ddc 100644 --- a/webapp/packages/core-theming/src/ComponentStyle.ts +++ b/webapp/packages/core-theming/src/ComponentStyle.ts @@ -7,8 +7,10 @@ */ import type { ClassCollection } from './themeUtils'; -export type BaseStyles = ClassCollection; -export type ThemeSelector = (theme: string) => Promise; -export type Style = BaseStyles | ThemeSelector; -export type DynamicStyle = Style | boolean | undefined; -export type ComponentStyle = DynamicStyle | DynamicStyle[] | DynamicStyle[][]; +export type BaseStyles = Record> = ClassCollection; +export type ThemeSelector = Record> = ( + theme: string, +) => Promise | BaseStyles[]>; +export type Style = Record> = BaseStyles | ThemeSelector; +export type DynamicStyle = Record> = Style | boolean | undefined; +export type ComponentStyle = Record> = DynamicStyle | ComponentStyle[]; diff --git a/webapp/packages/core-theming/src/ThemeService.ts b/webapp/packages/core-theming/src/ThemeService.ts index f2164fa6fc..fbf47723d4 100644 --- a/webapp/packages/core-theming/src/ThemeService.ts +++ b/webapp/packages/core-theming/src/ThemeService.ts @@ -13,6 +13,7 @@ import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { ServerConfigResource } from '@cloudbeaver/core-root'; import { SettingsService } from '@cloudbeaver/core-settings'; +import type { Style } from './ComponentStyle'; import './styles/main/base.pure.css'; import './styles/main/color.pure.scss'; import './styles/main/elevation.pure.scss'; @@ -37,6 +38,11 @@ interface ISettings { currentThemeId: string; } +export interface IStyleRegistry { + mode: 'replace' | 'append'; + styles: Style[]; +} + @injectable() export class ThemeService extends Bootstrap { get themes(): ITheme[] { @@ -67,6 +73,7 @@ export class ThemeService extends Bootstrap { readonly onChange: ISyncExecutor; + private readonly stylesRegistry: Map = new Map(); private readonly themeMap: Map = new Map(); private readonly settings: ISettings; @@ -95,6 +102,37 @@ export class ThemeService extends Bootstrap { this.loadAllThemes(); } + addStyleRegistry>(style: Style, mode: 'replace' | 'append', styles: Style[]): void { + if (!this.stylesRegistry.has(style)) { + this.stylesRegistry.set(style, []); + } + + this.stylesRegistry.get(style)!.push({ mode, styles }); + } + + mapStyles>(styles: Style[], context?: Map): Style[] { + return styles + .map(style => { + const registries = this.stylesRegistry.get(style) ?? context?.get(style); + + if (!registries) { + return [style]; + } + + return registries.reduce( + (acc, registry) => { + if (registry.mode === 'replace') { + acc = acc.filter(s => s !== style); + } + + return [...acc, ...this.mapStyles(registry.styles, context)] as Style[]; + }, + [style] as Style[], + ); + }) + .flat(); + } + async load(): Promise { await this.serverConfigResource.load(); this.setCurrentThemeId(this.defaultThemeId); // set default app theme diff --git a/webapp/packages/core-theming/src/styles/_button.scss b/webapp/packages/core-theming/src/styles/_button.scss index 9720df14e0..23fb1b9663 100644 --- a/webapp/packages/core-theming/src/styles/_button.scss +++ b/webapp/packages/core-theming/src/styles/_button.scss @@ -8,10 +8,12 @@ @mixin button-core-styles() { $mdc-button-disabled-container-fill-color: #cccccc; - $mdc-button-density-scale: -2; + $mdc-button-density-scale: -3; + $mdc-density-default-scale: -3; $query: mdc-feature-all(); @include mdc-button-core-styles; @include mdc-button-theme-baseline; + @include mdc-button-density(-3); .theme-button { @extend .mdc-button; @@ -84,4 +86,4 @@ @include mdc-states($color: on-secondary, $query: $query, $ripple-target: $mdc-button-ripple-target); } } -} \ No newline at end of file +} diff --git a/webapp/packages/core-theming/src/styles/_checkbox.scss b/webapp/packages/core-theming/src/styles/_checkbox.scss index 7593c51b61..3b020dee22 100644 --- a/webapp/packages/core-theming/src/styles/_checkbox.scss +++ b/webapp/packages/core-theming/src/styles/_checkbox.scss @@ -6,11 +6,14 @@ * you may not use this file except in compliance with the License. */ -@import "@material/checkbox/mixins"; +@import '@material/checkbox/mixins'; $mdc-checkbox-icon-size: 16px; +$mdc-checkbox-density-scale: -3; +$mdc-density-default-scale: -3; @mixin checkbox-core-styles($query: mdc-feature-all()) { @include mdc-checkbox-core-styles($query); + @include mdc-checkbox-density(-3); .theme-checkbox { @extend .mdc-checkbox; @@ -48,24 +51,14 @@ $mdc-checkbox-icon-size: 16px; $mdc-checkbox-mark-color: $mdc-theme-on-primary; @include mdc-checkbox-ink-color($mdc-checkbox-mark-color, $query); @include mdc-checkbox-disabled-ink-color(rgba($mdc-checkbox-mark-color, 0.5), $query); - @include mdc-checkbox-container-colors( - $mdc-checkbox-border-color, - transparent, - $mdc-theme-primary, - $mdc-theme-primary - ); + @include mdc-checkbox-container-colors($mdc-checkbox-border-color, transparent, $mdc-theme-primary, $mdc-theme-primary); } .theme-checkbox_surface { $mdc-checkbox-mark-color: $mdc-theme-primary; @include mdc-checkbox-ink-color($mdc-checkbox-mark-color, $query); @include mdc-checkbox-disabled-ink-color(rgba($mdc-checkbox-mark-color, 0.5), $query); - @include mdc-checkbox-container-colors( - $mdc-theme-surface, - transparent, - $mdc-theme-surface, - $mdc-theme-surface - ); + @include mdc-checkbox-container-colors($mdc-theme-surface, transparent, $mdc-theme-surface, $mdc-theme-surface); } .theme-checkbox_small { diff --git a/webapp/packages/core-theming/src/styles/_radio.scss b/webapp/packages/core-theming/src/styles/_radio.scss index 6b9e743d0a..1a7cb840c8 100644 --- a/webapp/packages/core-theming/src/styles/_radio.scss +++ b/webapp/packages/core-theming/src/styles/_radio.scss @@ -6,10 +6,15 @@ * you may not use this file except in compliance with the License. */ -@import "@material/radio/mixins"; +@import '@material/radio/mixins'; +$mdc-radio-density-scale: -3; +$mdc-density-default-scale: -3; +$mdc-radio-touch-area: 32px !default; +$mdc-radio-ui-size: 16px !default; @mixin radio-core-styles($query: mdc-feature-all()) { @include mdc-radio-core-styles; + @include mdc-radio-density(-3); .theme-radio { @extend .mdc-radio; @@ -64,4 +69,4 @@ .theme-radio_no-ripple { @include mdc-radio-ripple-size(0); } -} \ No newline at end of file +} diff --git a/webapp/packages/core-theming/src/styles/_switch.scss b/webapp/packages/core-theming/src/styles/_switch.scss index b424bbe4b7..da40dd1670 100644 --- a/webapp/packages/core-theming/src/styles/_switch.scss +++ b/webapp/packages/core-theming/src/styles/_switch.scss @@ -7,10 +7,12 @@ */ @import '@material/switch/mixins'; +$mdc-switch-density-scale: -3; +$mdc-density-default-scale: -3; @mixin switch-core-styles($query: mdc-feature-all()) { @include mdc-switch-core-styles($query); - + .theme-switch { @extend .mdc-switch; } @@ -41,7 +43,8 @@ @include switch-core-styles-dense($query); } - .theme-switch_dense .theme-switch_primary, .theme-switch_primary { + .theme-switch_dense .theme-switch_primary, + .theme-switch_primary { $mdc-switch-baseline-theme-color: primary; @include mdc-switch-toggled-on-track-color($mdc-switch-baseline-theme-color, $query); @@ -70,8 +73,7 @@ $mdc-switch-thumb-offset: 2px !global; - $mdc-switch-thumb-active-margin: - $mdc-switch-track-width - $mdc-switch-thumb-diameter + $mdc-switch-thumb-offset * 2 !global; + $mdc-switch-thumb-active-margin: $mdc-switch-track-width - $mdc-switch-thumb-diameter + $mdc-switch-thumb-offset * 2 !global; @include mdc-switch-core-styles($query); -} \ No newline at end of file +} diff --git a/webapp/packages/core-theming/src/styles/theme-dark.module.scss b/webapp/packages/core-theming/src/styles/theme-dark.module.scss index 63065ed8af..5a3604571c 100644 --- a/webapp/packages/core-theming/src/styles/theme-dark.module.scss +++ b/webapp/packages/core-theming/src/styles/theme-dark.module.scss @@ -20,5 +20,5 @@ @include form-controls; @include link; @include placeholder; -@include delimiter($mdc-theme-secondary); +@include delimiter($mdc-theme-background); @include branding-overflow; diff --git a/webapp/packages/core-theming/src/styles/theme-light.module.scss b/webapp/packages/core-theming/src/styles/theme-light.module.scss index 48c3b0546e..dd99c2d0af 100644 --- a/webapp/packages/core-theming/src/styles/theme-light.module.scss +++ b/webapp/packages/core-theming/src/styles/theme-light.module.scss @@ -20,5 +20,5 @@ @include form-controls; @include link; @include placeholder; -@include delimiter($mdc-theme-secondary); +@include delimiter($mdc-theme-background); @include branding-overflow; diff --git a/webapp/packages/core-theming/src/themeUtils.ts b/webapp/packages/core-theming/src/themeUtils.ts index 47e71d9a10..0ae949cd2a 100644 --- a/webapp/packages/core-theming/src/themeUtils.ts +++ b/webapp/packages/core-theming/src/themeUtils.ts @@ -6,9 +6,9 @@ * you may not use this file except in compliance with the License. */ -export interface ClassCollection { - [key: string]: any; -} +export type ClassCollection = Record> = { + [Key in keyof T]: string; +}; export class Composes { composes: ClassCollection; diff --git a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx index 22bcc2ea3b..37ee91209f 100644 --- a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx @@ -8,20 +8,8 @@ import { observer } from 'mobx-react-lite'; import { ButtonHTMLAttributes, forwardRef, useRef, useState } from 'react'; import type { MenuInitialState } from 'reakit/Menu'; -import styled from 'reshadow'; -import { - getComputed, - IMenuState, - IMouseContextMenu, - Menu, - menuPanelStyles, - useAutoLoad, - useObjectRef, - useStyles, - useTranslate, -} from '@cloudbeaver/core-blocks'; -import type { ComponentStyle } from '@cloudbeaver/core-theming'; +import { getComputed, IMenuState, IMouseContextMenu, Menu, useAutoLoad, useObjectRef, useTranslate } from '@cloudbeaver/core-blocks'; import { IMenuData, MenuActionItem } from '@cloudbeaver/core-view'; import { MenuItemRenderer } from './MenuItemRenderer'; @@ -33,10 +21,9 @@ interface IMenuProps extends React.PropsWithChildren { type ContextMenuRenderingChildren = (props: IMenuProps) => React.ReactNode | React.ReactElement; -interface IContextMenuProps extends Omit, 'style' | 'children'> { +interface IContextMenuProps extends Omit, 'children'> { mouseContextMenu?: IMouseContextMenu; menu: IMenuData; - style?: ComponentStyle; disclosure?: boolean; placement?: MenuInitialState['placement']; modal?: boolean; @@ -48,7 +35,7 @@ interface IContextMenuProps extends Omit, 'style' | 'c export const ContextMenu = observer( forwardRef(function ContextMenu( - { mouseContextMenu, menu: menuData, disclosure, children, style, placement, visible, onVisibleSwitch, modal, rtl, ...props }, + { mouseContextMenu, menu: menuData, disclosure, children, placement, visible, onVisibleSwitch, modal, rtl, ...props }, ref, ) { const translate = useTranslate(); @@ -61,7 +48,6 @@ export const ContextMenu = observer( const lazy = getComputed(() => !menuData.available || hidden); const menu = useRef(); - const styles = useStyles(menuPanelStyles, style); useAutoLoad(menuData.loaders, !lazy, menuVisible); @@ -92,7 +78,7 @@ export const ContextMenu = observer( const renderingChildren: React.ReactNode = typeof children === 'function' ? children({ loading, disabled }) : children; - return styled(styles)( + return ( ( menuData.items.map( item => menu.current && ( - + ), ) } @@ -122,13 +99,12 @@ export const ContextMenu = observer( placement={placement} disabled={disabled} disclosure={disclosure} - style={style} getHasBindings={handlers.hasBindings} onVisibleSwitch={handlers.handleVisibleSwitch} {...props} > {renderingChildren} - , + ); }), ); diff --git a/webapp/packages/core-ui/src/ContextMenu/IContextMenuItemProps.ts b/webapp/packages/core-ui/src/ContextMenu/IContextMenuItemProps.ts index 961876f42b..446be6196d 100644 --- a/webapp/packages/core-ui/src/ContextMenu/IContextMenuItemProps.ts +++ b/webapp/packages/core-ui/src/ContextMenu/IContextMenuItemProps.ts @@ -5,15 +5,10 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { MenuStateReturn } from 'reakit/ts'; - -import type { ComponentStyle } from '@cloudbeaver/core-theming'; import type { IMenuData, IMenuItem } from '@cloudbeaver/core-view'; export interface IContextMenuItemProps { item: IMenuItem; - menu: MenuStateReturn; menuData: IMenuData; - style?: ComponentStyle; onClick: () => void; } diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx index 4f2aab420f..bdcdd0dcfb 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx @@ -7,10 +7,8 @@ */ import { observer } from 'mobx-react-lite'; import React from 'react'; -import { MenuItem, MenuItemCheckbox, MenuItemRadio } from 'reakit/Menu'; -import styled, { use } from 'reshadow'; -import { Checkbox, MenuItemElement, menuPanelStyles, Radio, useStyles } from '@cloudbeaver/core-blocks'; +import { Checkbox, MenuItem, MenuItemCheckbox, MenuItemElement, MenuItemRadio, Radio } from '@cloudbeaver/core-blocks'; import type { IMenuActionItem } from '@cloudbeaver/core-view'; import type { IContextMenuItemProps } from './IContextMenuItemProps'; @@ -19,8 +17,7 @@ interface IMenuActionElementProps extends IContextMenuItemProps { item: IMenuActionItem; } -export const MenuActionElement = observer(function MenuActionElement({ item, menu, style, onClick }) { - const styles = useStyles(menuPanelStyles, style); +export const MenuActionElement = observer(function MenuActionElement({ item, onClick }) { const actionInfo = item.action.actionInfo; const loading = item.action.isLoading(); @@ -31,10 +28,9 @@ export const MenuActionElement = observer(function Menu if (actionInfo.type === 'select') { const checked = item.action.isChecked(); - return styled(styles)( + return ( , + ); } if (actionInfo.type === 'checkbox') { const checked = item.action.isChecked(); - return styled(styles)( + return ( , + ); } - return styled(styles)( - + return ( + , + ); }); diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts deleted file mode 100644 index 882f28b156..0000000000 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts +++ /dev/null @@ -1,70 +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 MENU_BAR_DEFAULT_STYLES = css` - menu-bar { - composes: theme-background-secondary theme-text-on-secondary theme-border-color-background theme-typography--body2 from global; - height: 48px; - display: flex; - } - - menu-bar-item { - composes: theme-ripple from global; - padding: 0 16px; - cursor: pointer; - background: transparent; - outline: none; - color: inherit; - - & menu-bar-item-box { - display: flex; - align-items: center; - } - - &[use|hidden] { - display: none; - } - - & menu-bar-item-icon { - display: flex; - width: 24px; - } - - & Loader, - & IconOrImage { - width: 24px; - } - - & menu-bar-item-label { - display: block; - text-transform: uppercase; - font-weight: 700; - } - - & menu-bar-item-icon + menu-bar-item-label { - padding-left: 8px; - } - - & menu-bar-item-mark { - display: none; - } - } - - MenuSeparator { - composes: theme-border-color-background from global; - height: 100%; - margin: 0; - border: 1px solid !important; - - &:first-child, - &:last-child { - display: none; - } - } -`; diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES.ts b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES.ts deleted file mode 100644 index 02883a757c..0000000000 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES.ts +++ /dev/null @@ -1,31 +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 MENU_BAR_SMALL_STYLES = css` - menu-bar-item { - padding: 0 12px; - - & menu-bar-item-icon { - display: flex; - width: 16px; - } - - & menu-bar-item-label { - font-size: 12px; - } - - & Loader, - & IconOrImage { - width: 16px; - } - } - menu-bar { - height: 32px; - } -`; diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.m.css b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.m.css new file mode 100644 index 0000000000..9b4480dc7b --- /dev/null +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.m.css @@ -0,0 +1,19 @@ +.menuBar { + height: 32px; + display: flex; + + & > .menuSeparator { + height: 100%; + margin: 0; + border: 1px solid !important; + + &:first-child, + &:last-child { + display: none; + } + } +} + +.floating.menuBar { + height: 28px; +} diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx index 739be1e7c6..3101d8caa0 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx @@ -7,11 +7,9 @@ */ import { observer } from 'mobx-react-lite'; import { forwardRef, useCallback } from 'react'; -import { MenuInitialState, MenuSeparator } from 'reakit'; -import styled, { use } from 'reshadow'; +import type { MenuInitialState } from 'reakit'; -import { getComputed, Loader, useAutoLoad, useStyles } from '@cloudbeaver/core-blocks'; -import type { ComponentStyle } from '@cloudbeaver/core-theming'; +import { getComputed, MenuSeparator, MenuSeparatorStyles, s, SContext, StyleRegistry, useAutoLoad, useS } from '@cloudbeaver/core-blocks'; import { DATA_CONTEXT_MENU_NESTED, DATA_CONTEXT_SUBMENU_ITEM, @@ -26,22 +24,32 @@ import { } from '@cloudbeaver/core-view'; import { ContextMenu } from '../ContextMenu'; +import style from './MenuBar.m.css'; import { MenuBarItem } from './MenuBarItem'; interface INestedMenuSettings extends MenuInitialState { onVisibleSwitch?: (visible: boolean) => void; } -interface IMenuBarProps extends Omit, 'style'> { +interface IMenuBarProps extends React.HTMLAttributes { menu: IMenuData; nestedMenuSettings?: INestedMenuSettings; - style?: ComponentStyle; rtl?: boolean; } +const registry: StyleRegistry = [ + [ + MenuSeparatorStyles, + { + mode: 'append', + styles: [style], + }, + ], +]; + export const MenuBar = observer( - forwardRef(function MenuBar({ menu, nestedMenuSettings, style, rtl, ...props }, ref) { - const styles = useStyles(style); + forwardRef(function MenuBar({ menu, nestedMenuSettings, rtl, className, ...props }, ref) { + const styles = useS(style); const items = menu.items; useAutoLoad(menu.loaders); @@ -49,12 +57,14 @@ export const MenuBar = observer( return null; } - return styled(styles)( - - {items.map(item => ( - - ))} - , + return ( + +
+ {items.map(item => ( + + ))} +
+
); }), ); @@ -65,12 +75,9 @@ interface IMenuBarElementProps { nestedMenuSettings?: INestedMenuSettings; className?: string; rtl?: boolean; - style?: ComponentStyle; } -const MenuBarElement = observer(function MenuBarElement({ item, menuData, nestedMenuSettings, className, rtl, style }) { - const styles = useStyles(style); - +const MenuBarElement = observer(function MenuBarElement({ item, menuData, nestedMenuSettings, className, rtl }) { const onClick = useCallback(() => { item.events?.onSelect?.(); }, [item]); @@ -80,33 +87,30 @@ const MenuBarElement = observer(function MenuBarElement({ } if (item instanceof MenuSubMenuItem) { - return styled(styles)( - , - ); + return ; } if (item instanceof MenuSeparatorItem) { - return styled(styles)(); + return ; } if (item instanceof MenuActionItem) { - return styled(styles)(); + return ; } if (item instanceof MenuBaseItem) { - return styled(styles)( + return (