diff --git a/common-react/@dbeaver/react-data-grid/src/DataGrid.css b/common-react/@dbeaver/react-data-grid/src/DataGrid.css index d6d795b020..0471601630 100644 --- a/common-react/@dbeaver/react-data-grid/src/DataGrid.css +++ b/common-react/@dbeaver/react-data-grid/src/DataGrid.css @@ -8,6 +8,10 @@ } @layer components.rdg.Cell { + .rdg { + --dbv-kit-icon-btn-outline-width: 1px; + } + @media (prefers-color-scheme: dark) { .rdg-cell-frozen { &:nth-last-child(1 of &) { diff --git a/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx b/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx index f253bf9eae..af69084940 100644 --- a/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx +++ b/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx @@ -49,8 +49,10 @@ export const DataGrid = forwardRef(function DataGrid headerText, getHeaderOrder, getHeaderResizable, + columnSortable, getHeaderHeight, getHeaderPinned, + columnSortingState, getHeaderDnD, cell, cellText, @@ -67,6 +69,7 @@ export const DataGrid = forwardRef(function DataGrid onScrollToBottom, onFocus, onCellChange, + onColumnSort, children, className, onCellKeyDown, @@ -141,7 +144,7 @@ export const DataGrid = forwardRef(function DataGrid - + boolean; getHeaderDnD?: (colIdx: number) => boolean; onHeaderReorder?: (from: number, to: number) => void; + columnSortingState?: IGridReactiveValue<'asc' | 'desc' | undefined | null, [colIdx: number]>; + columnSortable?: IGridReactiveValue; + onColumnSort?: (colIdx: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void; } export const DataGridCellHeaderContext = createContext(null); diff --git a/common-react/@dbeaver/react-data-grid/src/mapRenderHeaderCell.tsx b/common-react/@dbeaver/react-data-grid/src/mapRenderHeaderCell.tsx index 6c98d86727..01287f73c9 100644 --- a/common-react/@dbeaver/react-data-grid/src/mapRenderHeaderCell.tsx +++ b/common-react/@dbeaver/react-data-grid/src/mapRenderHeaderCell.tsx @@ -3,7 +3,7 @@ import type { IInnerRow } from './IInnerRow.js'; import { HeaderCellContentRenderer } from './renderers/HeaderCellContentRenderer.js'; export function mapRenderHeaderCell(colIdx: number) { - return function RenderHeaderCell({}: RenderHeaderCellProps) { - return ; + return function RenderHeaderCell({ tabIndex }: RenderHeaderCellProps) { + return ; }; } diff --git a/common-react/@dbeaver/react-data-grid/src/renderers/HeaderCellContentRenderer.tsx b/common-react/@dbeaver/react-data-grid/src/renderers/HeaderCellContentRenderer.tsx index 3833e6d18c..275d038cd8 100644 --- a/common-react/@dbeaver/react-data-grid/src/renderers/HeaderCellContentRenderer.tsx +++ b/common-react/@dbeaver/react-data-grid/src/renderers/HeaderCellContentRenderer.tsx @@ -1,17 +1,25 @@ -import { memo, use } from 'react'; +import { memo, use, useRef } from 'react'; import { useDrag, useDrop, type DnDStoreProvider } from '@dbeaver/react-dnd'; import { DataGridCellHeaderContext } from '../DataGridHeaderCellContext.js'; import { useGridReactiveValue } from '../useGridReactiveValue.js'; import { HeaderDnDContext } from '../useHeaderDnD.js'; +import { OrderButton } from './OrderButton.js'; interface Props { colIdx: number; + tabIndex?: number; } -export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer({ colIdx }: Props) { +export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer({ colIdx, tabIndex }: Props) { const dndHeaderContext = use(HeaderDnDContext); const cellHeaderContext = use(DataGridCellHeaderContext); const headerElement = useGridReactiveValue(cellHeaderContext?.headerElement, colIdx); const getHeaderText = useGridReactiveValue(headerElement ? undefined : cellHeaderContext?.headerText, colIdx); + const isColumnSortable = useGridReactiveValue(cellHeaderContext?.columnSortable, colIdx); + const onColumnSort = cellHeaderContext?.onColumnSort; + const sortingState = useGridReactiveValue(cellHeaderContext?.columnSortingState, colIdx); + + const orderButtonRef = useRef(null); + const containerRef = useRef(null); const draggable = dndHeaderContext?.getCanDrag?.(colIdx) ?? false; const drag = useDrag({ @@ -47,9 +55,26 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer }, }); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Tab' && !e.shiftKey && isColumnSortable && onColumnSort && orderButtonRef.current !== document.activeElement) { + e.preventDefault(); + e.stopPropagation(); + containerRef.current?.parentElement?.setAttribute('aria-selected', 'false'); + orderButtonRef.current?.focus(); + } + }; + return ( -
- {headerElement ?? getHeaderText ?? ''} +
+ {headerElement ?? getHeaderText ?? ''} + {isColumnSortable && onColumnSort && }
); }); diff --git a/common-react/@dbeaver/react-data-grid/src/renderers/OrderButton.tsx b/common-react/@dbeaver/react-data-grid/src/renderers/OrderButton.tsx new file mode 100644 index 0000000000..c152bbcd59 --- /dev/null +++ b/common-react/@dbeaver/react-data-grid/src/renderers/OrderButton.tsx @@ -0,0 +1,42 @@ +import { clsx, IconButton } from '@dbeaver/ui-kit'; + +interface OrderButtonProps { + colIdx: number; + sortState?: 'asc' | 'desc' | null; + onSort: (attributePosition: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void; + tabIndex?: number; + ref: React.Ref; +} + +export function OrderButton({ colIdx, sortState, onSort, tabIndex, ref }: OrderButtonProps) { + function handleClick(e: React.MouseEvent) { + const nextSortState = sortState === 'asc' ? 'desc' : sortState === 'desc' ? null : 'asc'; + onSort(colIdx, nextSortState, e.ctrlKey || e.metaKey); + } + + const svgSortAsc = '#icon-sort-asc'; + const svgSortDesc = '#icon-sort-desc'; + const svgSortUnknown = '#icon-sort-unknown'; + + const iconSrc = sortState === 'asc' ? svgSortAsc : sortState === 'desc' ? svgSortDesc : svgSortUnknown; + + return ( + + + + + + ); +} diff --git a/common-react/@dbeaver/ui-kit/.ladle/global.css b/common-react/@dbeaver/ui-kit/.ladle/global.css index 45f728e2bc..388b137073 100644 --- a/common-react/@dbeaver/ui-kit/.ladle/global.css +++ b/common-react/@dbeaver/ui-kit/.ladle/global.css @@ -1,4 +1,4 @@ -@import 'tailwindcss' prefix(tw); +@import 'tailwindcss' layer(base) prefix(tw); @layer theme { main { diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc.svg new file mode 100644 index 0000000000..f6bba7ac1d --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc_m.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc_m.svg new file mode 100644 index 0000000000..4c30067186 --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc_m.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc_sm.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc_sm.svg new file mode 100644 index 0000000000..b234902b13 --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-asc_sm.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc.svg new file mode 100644 index 0000000000..0c7611a56d --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc_m.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc_m.svg new file mode 100644 index 0000000000..40bdfc53ac --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc_m.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc_sm.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc_sm.svg new file mode 100644 index 0000000000..39a674aa15 --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-desc_sm.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted.svg new file mode 100644 index 0000000000..ad371b9ccc --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted_m.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted_m.svg new file mode 100644 index 0000000000..7ceba2dca7 --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted_m.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted_sm.svg b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted_sm.svg new file mode 100644 index 0000000000..46b4769f2f --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icon-sort-unsorted_sm.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/assets/icons/icons.svg b/common-react/@dbeaver/ui-kit/assets/icons/icons.svg new file mode 100644 index 0000000000..ecb80697f5 --- /dev/null +++ b/common-react/@dbeaver/ui-kit/assets/icons/icons.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/package.json b/common-react/@dbeaver/ui-kit/package.json index a1b3c22a37..3c499ed0c1 100644 --- a/common-react/@dbeaver/ui-kit/package.json +++ b/common-react/@dbeaver/ui-kit/package.json @@ -7,7 +7,8 @@ "exports": { ".": "./lib/index.js", "./*.css": "./src/*.css", - "./*": "./lib/*.js" + "./*": "./lib/*.js", + "./assets/icons/*.svg": "./assets/icons/*.svg" }, "scripts": { "build": "tsc -b", diff --git a/common-react/@dbeaver/ui-kit/scripts/icon-index-template.js b/common-react/@dbeaver/ui-kit/scripts/icon-index-template.js new file mode 100644 index 0000000000..9447d3bc25 --- /dev/null +++ b/common-react/@dbeaver/ui-kit/scripts/icon-index-template.js @@ -0,0 +1,16 @@ +export default function template(files) { + const exportLines = files.map((file) => { + const { path } = file; + + const fileName = path.split('/').pop(); + const componentNameWithExt = fileName.replace(/\.tsx$/, '.js'); + const componentName = fileName.replace(/\.tsx$/, ''); + + return `export { default as ${componentName} } from './${componentNameWithExt}';`; + }); + + + const header = '/* This file is automatically generated by the build:icons script. Do not modify it manually.*/\n\n '; + + return header + exportLines.join('\n'); +}; \ No newline at end of file diff --git a/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.css b/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.css index d67a0284b1..0708d50de6 100644 --- a/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.css +++ b/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.css @@ -7,6 +7,7 @@ min-width: var(--dbv-kit-icon-btn-size); width: var(--dbv-kit-icon-btn-size); height: var(--dbv-kit-icon-btn-size); + font-size: calc(var(--dbv-kit-icon-btn-size) * 0.75); border-radius: var(--dbv-kit-icon-btn-border-radius); border-color: var(--dbv-kit-icon-btn-border-color); @@ -56,14 +57,6 @@ } } - .dbv-kit-icon-button__icon { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - } - /* Size variants */ .dbv-kit-icon-button--small { --dbv-kit-icon-btn-size: var(--dbv-kit-icon-btn-small-size); diff --git a/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.tsx b/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.tsx index a8188d3677..b16bb171cc 100644 --- a/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.tsx +++ b/common-react/@dbeaver/ui-kit/src/IconButton/IconButton.tsx @@ -24,7 +24,7 @@ export function IconButtonBase({ className, variant = 'primary', size = 'medium' return ( - {children} + {children} ); } diff --git a/common-react/@dbeaver/ui-kit/src/IconButton/_base.css b/common-react/@dbeaver/ui-kit/src/IconButton/_base.css index bc50d2d385..1fa0b6e336 100644 --- a/common-react/@dbeaver/ui-kit/src/IconButton/_base.css +++ b/common-react/@dbeaver/ui-kit/src/IconButton/_base.css @@ -17,8 +17,8 @@ /* Default colors */ --dbv-kit-icon-btn-foreground: var(--tw-color-gray-600); - --dbv-kit-icon-btn-foreground-hover: var(--dbv-kit-color-600); - --dbv-kit-icon-btn-foreground-active: var(--dbv-kit-color-700); + --dbv-kit-icon-btn-foreground-hover: var(--tw-color-gray-600); + --dbv-kit-icon-btn-foreground-active: var(--tw-color-gray-700); --dbv-kit-icon-btn-background: transparent; --dbv-kit-icon-btn-background-hover: var(--tw-color-gray-100); --dbv-kit-icon-btn-background-active: var(--tw-color-gray-200); diff --git a/common-react/@dbeaver/ui-kit/src/stories/components/IconButton/icon-button.stories.tsx b/common-react/@dbeaver/ui-kit/src/stories/components/IconButton/icon-button.stories.tsx index 64abd23f07..f649e369c9 100644 --- a/common-react/@dbeaver/ui-kit/src/stories/components/IconButton/icon-button.stories.tsx +++ b/common-react/@dbeaver/ui-kit/src/stories/components/IconButton/icon-button.stories.tsx @@ -91,23 +91,23 @@ export const IconButtons = () => {

Primary

- - + + - - + + - - + + - - + +
@@ -116,23 +116,23 @@ export const IconButtons = () => {

Secondary

- - + + - - + + - - + + - - + +
@@ -141,23 +141,23 @@ export const IconButtons = () => {

Danger

- - + + - - + + - - + + - - + +
@@ -166,41 +166,41 @@ export const IconButtons = () => {

Disabled

- - + + - - + + - - + +

Loading

- - + + - - + + - - + + - - + +
diff --git a/webapp/packages/core-app/package.json b/webapp/packages/core-app/package.json index d7db7108b3..c84aa419b3 100644 --- a/webapp/packages/core-app/package.json +++ b/webapp/packages/core-app/package.json @@ -33,6 +33,7 @@ "@cloudbeaver/core-ui": "workspace:*", "@cloudbeaver/core-utils": "workspace:*", "@cloudbeaver/core-version": "workspace:*", + "@dbeaver/ui-kit": "workspace:^", "mobx": "^6", "mobx-react-lite": "^4", "react": "^19", diff --git a/webapp/packages/core-app/src/Body.tsx b/webapp/packages/core-app/src/Body.tsx index 03fcc3698b..613d167119 100644 --- a/webapp/packages/core-app/src/Body.tsx +++ b/webapp/packages/core-app/src/Body.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import { useAppVersion } from '@cloudbeaver/core-version'; import style from './Body.module.css'; import { useAppHeight } from './useAppHeight.js'; import { useClientActivity } from './useClientActivity.js'; +import icons from '@dbeaver/ui-kit/assets/icons/icons.svg?raw'; export const Body = observer(function Body() { // const serverConfigLoader = useResource(Body, ServerConfigResource, undefined); @@ -70,6 +71,7 @@ export const Body = observer(function Body() { +
diff --git a/webapp/packages/core-app/tsconfig.json b/webapp/packages/core-app/tsconfig.json index dd3d13ad71..9419181103 100644 --- a/webapp/packages/core-app/tsconfig.json +++ b/webapp/packages/core-app/tsconfig.json @@ -7,6 +7,9 @@ "composite": true }, "references": [ + { + "path": "../../../common-react/@dbeaver/ui-kit" + }, { "path": "../core-blocks" }, diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css index cd70decec4..c79dd0c8cc 100644 --- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css +++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.module.css @@ -57,8 +57,3 @@ text-overflow: ellipsis; white-space: nowrap; } - -.reject { - cursor: pointer; - flex-shrink: 0; -} diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx index 88df8bf1a0..310e6684ab 100644 --- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx +++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogHeader.tsx @@ -1,13 +1,14 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import { ActionIconButton } from '../../ActionIconButton.js'; +import { IconButton } from '@dbeaver/ui-kit'; +import { Icon } from '../../Icon.js'; import { IconOrImage } from '../../IconOrImage.js'; import { useTranslate } from '../../localization/useTranslate.js'; import { s } from '../../s.js'; @@ -46,9 +47,9 @@ export const CommonDialogHeader = observer(function CommonDialogHeader({

{translate(title)}

{onReject && ( -
- -
+ + + )}
{subTitle &&
{typeof subTitle === 'string' ? translate(subTitle) : subTitle}
} diff --git a/webapp/packages/core-theming/src/ThemeService.ts b/webapp/packages/core-theming/src/ThemeService.ts index b824358c46..dd28f8c316 100644 --- a/webapp/packages/core-theming/src/ThemeService.ts +++ b/webapp/packages/core-theming/src/ThemeService.ts @@ -20,6 +20,7 @@ import './styles/main/fonts.pure.css'; import './styles/main/normalize.pure.css'; import './styles/main/typography.pure.scss'; import './styles/UiCheckbox.css'; +import './styles/UiIconButton.css'; import { DEFAULT_THEME_ID, themes } from './themes.js'; import { ThemeSettingsService } from './ThemeSettingsService.js'; import type { ClassCollection } from './themeUtils.js'; diff --git a/webapp/packages/core-theming/src/styles/UiCheckbox.css b/webapp/packages/core-theming/src/styles/UiCheckbox.css index 67a23e1cb4..6514157773 100644 --- a/webapp/packages/core-theming/src/styles/UiCheckbox.css +++ b/webapp/packages/core-theming/src/styles/UiCheckbox.css @@ -1,4 +1,3 @@ -@import url('@dbeaver/ui-kit/index.css'); @layer components { body { --dbv-kit-checkbox-background: var(--theme-surface); diff --git a/webapp/packages/core-theming/src/styles/UiIconButton.css b/webapp/packages/core-theming/src/styles/UiIconButton.css new file mode 100644 index 0000000000..a39d1f855a --- /dev/null +++ b/webapp/packages/core-theming/src/styles/UiIconButton.css @@ -0,0 +1,21 @@ +@layer components { + .dbv-kit-icon-button { + --dbv-kit-color-primary-500: var(--theme-primary); + --dbv-kit-icon-btn-outline-color: var(--theme-primary); + + --dbv-kit-icon-btn-primary-foreground: var(--theme-primary); + --dbv-kit-icon-btn-primary-foreground-hover: var(--theme-primary); + --dbv-kit-icon-btn-primary-foreground-active: var(--theme-primary); + --dbv-kit-icon-btn-primary-background: transparent; + --dbv-kit-icon-btn-primary-background-hover: var(--theme-secondary); + --dbv-kit-icon-btn-primary-background-active: var(--theme-background); + + --dbv-kit-icon-btn-secondary-background: transparent; + --dbv-kit-icon-btn-secondary-foreground: var(--theme-on-secondary); + --dbv-kit-icon-btn-secondary-foreground-hover: var(--theme-on-secondary); + --dbv-kit-icon-btn-secondary-foreground-active: var(--theme-on-secondary); + --dbv-kit-icon-btn-secondary-background-hover: var(--theme-secondary, #f2f2f2); + --dbv-kit-icon-btn-secondary-background-active: var(--theme-background); + --dbv-kit-icon-btn-secondary-border-color: transparent; + } +} diff --git a/webapp/packages/core-theming/src/styles/main/light.theme.scss b/webapp/packages/core-theming/src/styles/main/light.theme.scss index ea11be6cc4..c2dab02675 100644 --- a/webapp/packages/core-theming/src/styles/main/light.theme.scss +++ b/webapp/packages/core-theming/src/styles/main/light.theme.scss @@ -8,20 +8,22 @@ @import '../theme-light'; -@include theme { - @include colors; - @include color-scheme; - @include css-variables; - @include ripple-core-styles; - @include theme-ripple; - @include layout-grid; - @include form-controls; - @include button-core-styles; - @include radio-core-styles; - @include switch-core-styles; - @include checkbox-core-styles; - @include link; - @include placeholder; - @include delimiter($mdc-theme-secondary); - @include branding-overflow; -} +@layer base { + @include theme { + @include colors; + @include color-scheme; + @include css-variables; + @include ripple-core-styles; + @include theme-ripple; + @include layout-grid; + @include form-controls; + @include button-core-styles; + @include radio-core-styles; + @include switch-core-styles; + @include checkbox-core-styles; + @include link; + @include placeholder; + @include delimiter($mdc-theme-secondary); + @include branding-overflow; + } +} \ No newline at end of file diff --git a/webapp/packages/core-theming/src/styles/main/normalize.pure.css b/webapp/packages/core-theming/src/styles/main/normalize.pure.css index 9181bfb61f..9ba516029c 100644 --- a/webapp/packages/core-theming/src/styles/main/normalize.pure.css +++ b/webapp/packages/core-theming/src/styles/main/normalize.pure.css @@ -14,7 +14,7 @@ * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in iOS. */ - +@layer base { html { line-height: 1.15; /* 1 */ @@ -405,3 +405,4 @@ template { fill: currentColor; overflow: hidden; } +} \ No newline at end of file diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx index f767ed5d03..fcef4babda 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -38,6 +38,9 @@ import { ResultSetDataSource, ResultSetSelectAction, ResultSetViewAction, + DatabaseDataConstraintAction, + getNextOrder, + isResultSetDataModel, } from '@cloudbeaver/plugin-data-viewer'; import { CellRenderer } from './CellRenderer/CellRenderer.js'; @@ -77,6 +80,7 @@ export const DataGridTable = observer(function DataGridT const selectionAction = (model.source as unknown as ResultSetDataSource).getAction(resultIndex, ResultSetSelectAction); const viewAction = (model.source as unknown as ResultSetDataSource).getAction(resultIndex, ResultSetViewAction); + const constraintsAction = (model.source as unknown as ResultSetDataSource).tryGetAction(resultIndex, DatabaseDataConstraintAction); const tableData = useTableData(model as unknown as IDatabaseDataModel, resultIndex, dataGridDivRef); const gridSelectionContext = useGridSelectionContext(tableData, selectionAction); @@ -205,7 +209,7 @@ export const DataGridTable = observer(function DataGridT return () => { tableData.editor.action.removeHandler(syncEditor); }; - }, [tableData.editor, selectionAction]); + }, [tableData.editor, selectionAction, handlers, tableData]); const handleFocusChange = (position: ICellPosition) => { focusedCell.current = position; @@ -249,7 +253,7 @@ export const DataGridTable = observer(function DataGridT getDataGridApi: () => dataGridRef.current, focus: restoreFocus, }), - [model, actions, resultIndex, simple, dataGridRef, gridContainerRef, restoreFocus], + [model, actions, resultIndex, simple, dataGridRef, restoreFocus], ); const columnsCount = useCreateGridReactiveValue( @@ -334,6 +338,50 @@ export const DataGridTable = observer(function DataGridT [], ); + function getColumnSortable(colIdx: number) { + return ( + Boolean(tableData.getColumn(colIdx) && constraintsAction?.supported && isResultSetDataModel(model) && !model.isDisabled(resultIndex)) && + colIdx !== 0 + ); + } + + const columnSortable = useCreateGridReactiveValue( + getColumnSortable, + (onValueChange, colIdx) => reaction(() => getColumnSortable(colIdx), onValueChange), + [tableData, constraintsAction, model], + ); + + function getColumnSortingState(colIdx: number) { + const column = tableData.getColumn(colIdx)?.key; + if (!column || !constraintsAction) { + return null; + } + const resultColumn = tableData.getColumnInfo(column); + return resultColumn ? constraintsAction?.getOrder(resultColumn.position) : null; + } + + const columnSortingState = useCreateGridReactiveValue( + getColumnSortingState, + (onValueChange, colIdx) => reaction(() => getColumnSortingState(colIdx), onValueChange), + [tableData, constraintsAction, model], + ); + + function handleSort(colIdx: number, order: 'asc' | 'desc' | null, isMultiple: boolean) { + const column = tableData.getColumn(colIdx)?.key; + if (!column || !constraintsAction) { + return; + } + const resultColumn = tableData.getColumnInfo(column); + if (!resultColumn) { + return; + } + const currentOrder = constraintsAction!.getOrder(resultColumn.position); + const nextOrder = getNextOrder(currentOrder); + model.request(() => { + constraintsAction!.setOrder(resultColumn.position, nextOrder, isMultiple); + }); + } + function handleCellChange(rowIdx: number, colIdx: number, value: string) { const row = tableData.rows[rowIdx]; const column = tableData.getColumn(colIdx)?.key; @@ -478,13 +526,16 @@ export const DataGridTable = observer(function DataGridT getHeaderResizable={getHeaderResizable} getRowHeight={() => rowHeight} getColumnKey={getColumnKey} - onCellKeyDown={handleCellKeyDown} columnCount={columnsCount} rowCount={rowsCount} + columnSortable={columnSortable} + columnSortingState={columnSortingState} getRowId={rowIdx => (tableData.rows[rowIdx] ? ResultSetDataKeysUtils.serialize(tableData.rows[rowIdx]) : '')} onFocus={handleFocusChange} onScrollToBottom={handleScrollToBottom} + onColumnSort={handleSort} onCellChange={handleCellChange} + onCellKeyDown={handleCellKeyDown} />
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx deleted file mode 100644 index c3d6cce1dd..0000000000 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { observer } from 'mobx-react-lite'; - -import { ActionIconButton, useTranslate } from '@cloudbeaver/core-blocks'; -import { DatabaseDataConstraintAction, EOrder, getNextOrder, type IDatabaseDataModel, ResultSetDataSource } from '@cloudbeaver/plugin-data-viewer'; -import { clsx } from '@dbeaver/ui-kit'; - -interface Props { - model: IDatabaseDataModel; - resultIndex: number; - attributePosition: number; - className?: string; -} - -export const OrderButton = observer(function OrderButton({ model, resultIndex, attributePosition, className }) { - const translate = useTranslate(); - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); - const currentOrder = constraints.getOrder(attributePosition); - const disabled = model.isDisabled(resultIndex) || model.isLoading(); - - let icon = 'order-arrow-unknown'; - if (currentOrder === EOrder.asc) { - icon = 'order-arrow-asc'; - } else if (currentOrder === EOrder.desc) { - icon = 'order-arrow-desc'; - } - - const handleSort = async (e: React.MouseEvent) => { - e.stopPropagation(); - const nextOrder = getNextOrder(currentOrder); - await model.request(() => { - constraints.setOrder(attributePosition, nextOrder, e.ctrlKey || e.metaKey); - }); - }; - - function preventFocus(event: React.MouseEvent) { - event.preventDefault(); - } - - return ( - - ); -}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx index a10366555e..fa5618b06e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx @@ -9,13 +9,10 @@ import { observer } from 'mobx-react-lite'; import { useContext } from 'react'; import { getComputed, s, StaticImage, useS } from '@cloudbeaver/core-blocks'; -import type { SqlResultColumn } from '@cloudbeaver/core-sdk'; -import { DatabaseDataConstraintAction, isResultSetDataModel, ResultSetDataSource } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContext } from '../DataGridContext.js'; import { DataGridSelectionContext } from '../DataGridSelection/DataGridSelectionContext.js'; import { TableDataContext } from '../TableDataContext.js'; -import { OrderButton } from './OrderButton.js'; import style from './TableColumnHeader.module.css'; import { useTableColumnDnD } from './useTableColumnDnD.js'; @@ -34,17 +31,10 @@ export const TableColumnHeader = observer(function TableColumnHeader({ co const columnInfo = tableDataContext.getColumn(colIdx)!; const dnd = useTableColumnDnD(model, resultIndex, columnInfo.key); - let constraintsAction: DatabaseDataConstraintAction | undefined; - - if (isResultSetDataModel(model)) { - constraintsAction = (model.source as ResultSetDataSource).tryGetAction(resultIndex, DatabaseDataConstraintAction); - } const dataReadonly = getComputed(() => model.isReadonly(resultIndex)); const hasElementIdentifier = getComputed(() => model.hasElementIdentifier(resultIndex)); - const sortingDisabled = getComputed(() => !constraintsAction?.supported || model.isDisabled(resultIndex)); - let resultColumn: SqlResultColumn | undefined; let icon: string | undefined; let columnName: string | undefined; let columnReadOnly = false; @@ -54,7 +44,6 @@ export const TableColumnHeader = observer(function TableColumnHeader({ co const column = tableDataContext.data.getColumn(columnInfo.key); if (column) { - resultColumn = column; columnName = column.label!; icon = column.icon; columnReadOnly ||= tableDataContext.format.isReadOnly({ column: columnInfo.key }); @@ -96,9 +85,6 @@ export const TableColumnHeader = observer(function TableColumnHeader({ co )}
{columnName}
- {!sortingDisabled && resultColumn && isResultSetDataModel(model) && ( - - )} ); }); diff --git a/webapp/packages/product-base/src/baseConfigurationPlugin.ts b/webapp/packages/product-base/src/baseConfigurationPlugin.ts index 97766a29bc..97997a5e06 100644 --- a/webapp/packages/product-base/src/baseConfigurationPlugin.ts +++ b/webapp/packages/product-base/src/baseConfigurationPlugin.ts @@ -84,6 +84,8 @@ export function baseConfigurationPlugin(mode: string, packageJson: any): PluginO modulePreload: false, /* We need to disable css splitting because of the issue with loading CSS for some chunks (dbeaver/pro#5599), can be removed when dbeaver/pro#5204 is done */ cssCodeSplit: false, + /* Imported or referenced assets that are smaller than this threshold will be inlined as base64 URLs to avoid extra http requests. Set to 0 to disable inlining altogether. */ + assetsInlineLimit: 0, rollupOptions: { ...config.build?.rollupOptions, diff --git a/webapp/packages/product-default/src/styles.css b/webapp/packages/product-default/src/styles.css index 910080db92..c16b414fdc 100644 --- a/webapp/packages/product-default/src/styles.css +++ b/webapp/packages/product-default/src/styles.css @@ -1,2 +1,2 @@ -@import 'tailwindcss' prefix(tw); -@custom-variant dark (&:where(.theme-dark, .theme-dark *)); \ No newline at end of file +@import 'tailwindcss' layer(base) prefix(tw); +@custom-variant dark (&:where(.theme-dark, .theme-dark *)); diff --git a/webapp/yarn.lock b/webapp/yarn.lock index b76cca1ffa..9d53b9e37e 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -1427,6 +1427,7 @@ __metadata: "@cloudbeaver/core-utils": "workspace:*" "@cloudbeaver/core-version": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" + "@dbeaver/ui-kit": "workspace:^" "@types/react": "npm:^19" mobx: "npm:^6" mobx-react-lite: "npm:^4"