mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Cb 3960 migrate core blocks components to css modules (#2578)
* CB-3960 removes unused SlideBox styles * CB-3960 makes menu panel reshadowless * CB-3960 makes menu bar small item reshadowless * CB-3960 removes reshadow from loaders * fix: loader styles are the same as was * fix: CB-3960 fix hidden actions in table footer * fix: menu trigger --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
This commit is contained in:
co-authored by
mr-anton-t
parent
1f3ae6a508
commit
c8457c2004
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.loader {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: opacity cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
|
||||
&:global(.animate) {
|
||||
opacity: 1;
|
||||
}
|
||||
& .staticImage {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
&.secondary,
|
||||
&.overlay,
|
||||
&:global(.secondary),
|
||||
&:global(.overlay) {
|
||||
&:not(.small):not(:global(.small)) {
|
||||
& .staticImage.secondaryIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&.small,
|
||||
&:global(.small) {
|
||||
& .staticImage.secondarySmallIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(.secondary):not(.overlay):not(:global(.secondary)):not(:global(.overlay)) {
|
||||
&:not(.small):not(:global(.small)) {
|
||||
& .staticImage.primaryIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&.small,
|
||||
&:global(.small) {
|
||||
& .staticImage.primarySmallIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: rotation 2s infinite linear;
|
||||
}
|
||||
.message {
|
||||
padding: 16px;
|
||||
}
|
||||
.actions {
|
||||
padding-top: 42px;
|
||||
}
|
||||
.loader.inline {
|
||||
height: 38px;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
justify-content: left;
|
||||
& .icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
& .message {
|
||||
display: block;
|
||||
padding: 0 16px;
|
||||
}
|
||||
& .actions {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.loader.small {
|
||||
& .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
& .message {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.loader.fullSize {
|
||||
height: 100%;
|
||||
& .icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loaderOverlay {
|
||||
composes: theme-text-on-primary from global;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
@@ -8,19 +8,18 @@
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Suspense, useContext, useEffect, useRef, useState } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { ILoadableState, uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { ExceptionMessage } from '../ExceptionMessage';
|
||||
import { Translate } from '../localization/Translate';
|
||||
import { s } from '../s';
|
||||
import { StaticImage } from '../StaticImage';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { useS } from '../useS';
|
||||
import styles from './Loader.m.css';
|
||||
import { ILoaderContext, LoaderContext } from './LoaderContext';
|
||||
import { loaderStyles, overlayStyles } from './loaderStyles';
|
||||
|
||||
type LoaderState =
|
||||
| ILoadableState
|
||||
@@ -52,7 +51,6 @@ interface Props {
|
||||
className?: string;
|
||||
fullSize?: boolean;
|
||||
state?: LoaderState | LoaderState[];
|
||||
style?: ComponentStyle;
|
||||
children?: (() => React.ReactNode) | React.ReactNode;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
@@ -80,7 +78,6 @@ export const Loader = observer<Props>(function Loader({
|
||||
loading,
|
||||
inlineException,
|
||||
state,
|
||||
style,
|
||||
children,
|
||||
onCancel,
|
||||
}) {
|
||||
@@ -171,7 +168,7 @@ export const Loader = observer<Props>(function Loader({
|
||||
loading = false;
|
||||
}
|
||||
|
||||
style = useStyles(loaderStyles, style, overlay && overlayStyles);
|
||||
const style = useS(styles);
|
||||
const [isVisible, setVisible] = useState(loading);
|
||||
|
||||
const refLoaderDisplayed = { state: false };
|
||||
@@ -232,7 +229,6 @@ export const Loader = observer<Props>(function Loader({
|
||||
fullSize={fullSize}
|
||||
className={className}
|
||||
inlineException={inlineException}
|
||||
style={style}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@@ -251,7 +247,7 @@ export const Loader = observer<Props>(function Loader({
|
||||
if (hideException) {
|
||||
return null;
|
||||
}
|
||||
return styled(style)(<ExceptionMessage exception={exception} inline={inline || inlineException} className={className} onRetry={reload} />);
|
||||
return <ExceptionMessage exception={exception} inline={inline || inlineException} className={className} onRetry={reload} />;
|
||||
}
|
||||
|
||||
if (children && (!loader || !loading) && !overlay) {
|
||||
@@ -274,31 +270,31 @@ export const Loader = observer<Props>(function Loader({
|
||||
|
||||
refLoaderDisplayed.state = true;
|
||||
|
||||
return styled(style)(
|
||||
return (
|
||||
<LoaderContext.Provider value={contextState}>
|
||||
<>
|
||||
{overlay && renderWrappedChildren()}
|
||||
<loader ref={loaderRef} className={className} {...use({ small, fullSize, inline, secondary, overlay })}>
|
||||
<icon>
|
||||
<StaticImage icon={spinnerType.primary} {...use({ primaryIcon: true })} />
|
||||
<StaticImage icon={spinnerType.primarySmall} {...use({ primarySmallIcon: true })} />
|
||||
<StaticImage icon={spinnerType.secondary} {...use({ secondaryIcon: true })} />
|
||||
<StaticImage icon={spinnerType.secondarySmall} {...use({ secondarySmallIcon: true })} />
|
||||
</icon>
|
||||
<div ref={loaderRef} className={s(style, { loader: true, loaderOverlay: overlay, small, fullSize, inline, secondary, overlay }, className)}>
|
||||
<div className={s(style, { icon: true })}>
|
||||
<StaticImage icon={spinnerType.primary} className={s(style, { staticImage: true, primaryIcon: true })} />
|
||||
<StaticImage icon={spinnerType.primarySmall} className={s(style, { staticImage: true, primarySmallIcon: true })} />
|
||||
<StaticImage icon={spinnerType.secondary} className={s(style, { staticImage: true, secondaryIcon: true })} />
|
||||
<StaticImage icon={spinnerType.secondarySmall} className={s(style, { staticImage: true, secondarySmallIcon: true })} />
|
||||
</div>
|
||||
{!hideMessage && (
|
||||
<message>
|
||||
<div className={s(style, { message: true })}>
|
||||
<Translate token={message || 'ui_processing_loading'} />
|
||||
</message>
|
||||
</div>
|
||||
)}
|
||||
{onCancel && (
|
||||
<actions>
|
||||
<div className={s(style, { actions: true })}>
|
||||
<Button type="button" mod={['unelevated']} disabled={cancelDisabled} onClick={onCancel}>
|
||||
<Translate token="ui_processing_cancel" />
|
||||
</Button>
|
||||
</actions>
|
||||
</div>
|
||||
)}
|
||||
</loader>
|
||||
</div>
|
||||
</>
|
||||
</LoaderContext.Provider>,
|
||||
</LoaderContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 loaderStyles = css`
|
||||
loader {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: opacity cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
|
||||
|
||||
&:global(.animate) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& StaticImage {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[|secondary],
|
||||
&[|overlay],
|
||||
&:global(.secondary),
|
||||
&:global(.overlay) {
|
||||
&:not([|small]):not(:global(.small)) {
|
||||
& StaticImage[|secondaryIcon] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&[|small],
|
||||
&:global(.small) {
|
||||
& StaticImage[|secondarySmallIcon] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not([|secondary]):not([|overlay]):not(:global(.secondary)):not(:global(.overlay)) {
|
||||
&:not([|small]):not(:global(.small)) {
|
||||
& StaticImage[|primaryIcon] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&[|small],
|
||||
&:global(.small) {
|
||||
& StaticImage[|primarySmallIcon] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: rotation 2s infinite linear;
|
||||
}
|
||||
|
||||
message {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
actions {
|
||||
padding-top: 42px;
|
||||
}
|
||||
|
||||
loader[|inline] {
|
||||
height: 38px;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
justify-content: left;
|
||||
|
||||
& icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
& message {
|
||||
display: block;
|
||||
padding: 0 16px;
|
||||
}
|
||||
& actions {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
loader[|small] {
|
||||
& icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
& message {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
loader[|fullSize] {
|
||||
height: 100%;
|
||||
|
||||
& icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const overlayStyles = css`
|
||||
loader {
|
||||
composes: theme-text-on-primary from global;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
`;
|
||||
@@ -11,4 +11,25 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.iconBox {
|
||||
composes: theme-form-element-radius theme-text-primary theme-ripple from global;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 4px !important;
|
||||
margin: 2px !important;
|
||||
flex-shrink: 0;
|
||||
height: 24px !important;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
|
||||
& .iconLabel {
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,43 +6,15 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import type { ButtonProps } from 'reakit/ts';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { IconButton } from '../IconButton';
|
||||
import { useStyles } from '../useStyles';
|
||||
import moduleStyles from './MenuBarSmallItem.m.css';
|
||||
import { useS } from '../useS';
|
||||
import { s } from '../s';
|
||||
|
||||
export const styles = css`
|
||||
icon-box {
|
||||
composes: theme-form-element-radius theme-text-primary theme-ripple from global;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 4px !important;
|
||||
margin: 2px !important;
|
||||
flex-shrink: 0;
|
||||
height: 24px !important;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
|
||||
& icon-label {
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
`;
|
||||
import { useS } from '../useS';
|
||||
import moduleStyles from './MenuBarSmallItem.m.css';
|
||||
|
||||
interface Props extends Omit<ButtonProps, 'style' | 'icon'> {
|
||||
icon?: string;
|
||||
viewBox?: string;
|
||||
style?: ComponentStyle;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
@@ -51,17 +23,16 @@ export const MenuBarSmallItem: React.FC<React.PropsWithChildren<Props>> = functi
|
||||
viewBox,
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
onClick,
|
||||
...rest
|
||||
}) {
|
||||
const moduleStyle = useS(moduleStyles);
|
||||
const style = useS(moduleStyles);
|
||||
// TODO: replace IconButton with StaticImage / Icon
|
||||
// TODO: use button for icon-box (maybe)
|
||||
return styled(useStyles(styles, style))(
|
||||
<icon-box className={className} tabIndex={0} onClick={onClick}>
|
||||
{icon && <IconButton className={s(moduleStyle, { iconButton: true })} name={icon} viewBox={viewBox} {...rest} />}
|
||||
{children && <icon-label>{children}</icon-label>}
|
||||
</icon-box>,
|
||||
return (
|
||||
<div className={s(style, { iconBox: true }, className)} tabIndex={0} onClick={onClick}>
|
||||
{icon && <IconButton className={s(style, { iconButton: true })} name={icon} viewBox={viewBox} {...rest} />}
|
||||
{children && <div className={s(style, { iconLabel: true })}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import type { IMenuItem } from '@cloudbeaver/core-dialogs';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { Checkbox } from '../FormControls/Checkboxes/Checkbox';
|
||||
import { Radio } from '../FormControls/Radio';
|
||||
@@ -17,16 +15,18 @@ import { Icon } from '../Icon';
|
||||
import { IconOrImage } from '../IconOrImage';
|
||||
import { Loader } from '../Loader/Loader';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { menuPanelStyles } from './menuPanelStyles';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import MenuPanelItemAndTriggerStyles from './shared/MenuPanelItemAndTrigger.m.css';
|
||||
|
||||
interface MenuPanelItemProps {
|
||||
menuItem: IMenuItem;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const MenuPanelItem = observer<MenuPanelItemProps>(function MenuPanelItem({ menuItem, style = [] }) {
|
||||
export const MenuPanelItem = observer<MenuPanelItemProps>(function MenuPanelItem({ menuItem }) {
|
||||
const translate = useTranslate();
|
||||
const style = useS(MenuPanelItemAndTriggerStyles);
|
||||
|
||||
const title = translate(menuItem.title);
|
||||
let control = null;
|
||||
@@ -37,13 +37,22 @@ export const MenuPanelItem = observer<MenuPanelItemProps>(function MenuPanelItem
|
||||
control = <Checkbox checked={menuItem.isChecked} mod={['primary', 'small']} style={style} ripple={false} />;
|
||||
}
|
||||
|
||||
return styled(useStyles(menuPanelStyles, style))(
|
||||
<menu-panel-item {...use({ separator: menuItem.separator })}>
|
||||
<menu-item-content>{menuItem.icon ? <IconOrImage icon={menuItem.icon} /> : control}</menu-item-content>
|
||||
<menu-item-text title={title}>{title}</menu-item-text>
|
||||
<menu-item-content>
|
||||
{menuItem.panel && (menuItem.isProcessing ? <Loader small fullSize /> : <Icon name="arrow" viewBox="0 0 16 16" />)}
|
||||
</menu-item-content>
|
||||
</menu-panel-item>,
|
||||
return (
|
||||
<div className={s(style, { menuPanelItem: true, separator: menuItem.separator })}>
|
||||
<div className={s(style, { menuItemContent: true })}>
|
||||
{menuItem.icon ? <IconOrImage className={s(style, { iconOrImage: true })} icon={menuItem.icon} /> : control}
|
||||
</div>
|
||||
<div className={s(style, { menuItemText: true })} title={title}>
|
||||
{title}
|
||||
</div>
|
||||
<div className={s(style, { menuItemContent: true })}>
|
||||
{menuItem.panel &&
|
||||
(menuItem.isProcessing ? (
|
||||
<Loader className={s(style, { loader: true })} small fullSize />
|
||||
) : (
|
||||
<Icon className={s(style, { icon: true })} name="arrow" viewBox="0 0 16 16" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,15 +9,14 @@ import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { ButtonHTMLAttributes, forwardRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Menu, MenuButton, MenuInitialState, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuStateReturn, useMenuState } from 'reakit/Menu';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import type { IMenuItem, IMenuPanel } from '@cloudbeaver/core-dialogs';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { s } from '../s';
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { useS } from '../useS';
|
||||
import { MenuPanelItem } from './MenuPanelItem';
|
||||
import { menuPanelStyles } from './menuPanelStyles';
|
||||
import MenuPanelItemAndTriggerStyles from './shared/MenuPanelItemAndTrigger.m.css';
|
||||
|
||||
export type MenuState = MenuStateReturn;
|
||||
|
||||
@@ -26,7 +25,6 @@ export type MenuState = MenuStateReturn;
|
||||
*/
|
||||
|
||||
interface IMenuTriggerBaseProps extends Omit<ButtonHTMLAttributes<any>, 'style'> {
|
||||
style?: ComponentStyle;
|
||||
menuRef?: React.RefObject<MenuState | undefined>;
|
||||
disclosure?: boolean;
|
||||
placement?: MenuInitialState['placement'];
|
||||
@@ -44,15 +42,16 @@ interface IMenuTriggerLazyProps extends IMenuTriggerBaseProps {
|
||||
interface IMenuTriggerProps extends IMenuTriggerBaseProps {
|
||||
panel: IMenuPanel;
|
||||
getPanel?: () => IMenuPanel;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const MenuTrigger = React.forwardRef<ButtonHTMLAttributes<any>, IMenuTriggerProps | IMenuTriggerLazyProps>(function MenuTrigger(
|
||||
{ panel, menuRef, getPanel, disclosure, children, style, placement, visible, onVisibleSwitch, modal, rtl, ...props },
|
||||
{ panel, menuRef, getPanel, disclosure, className, children, placement, visible, onVisibleSwitch, modal, rtl, ...props },
|
||||
ref,
|
||||
) {
|
||||
const propsRef = useObjectRef({ onVisibleSwitch, visible });
|
||||
const menu = useMenuState({ modal, placement, visible, rtl });
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
const style = useS(MenuPanelItemAndTriggerStyles);
|
||||
|
||||
if (menuRef) {
|
||||
//@ts-expect-error ref mutation
|
||||
@@ -73,22 +72,22 @@ export const MenuTrigger = React.forwardRef<ButtonHTMLAttributes<any>, IMenuTrig
|
||||
|
||||
if (React.isValidElement(children) && disclosure) {
|
||||
return (
|
||||
<>
|
||||
<MenuButton ref={ref} {...menu} {...props} {...children.props}>
|
||||
<div className={s(style, { menuTrigger: true }, className)}>
|
||||
<MenuButton ref={ref} className={s(style, { menuButton: true })} {...menu} {...props} {...children.props}>
|
||||
{disclosureProps => React.cloneElement(children, disclosureProps)}
|
||||
</MenuButton>
|
||||
{panel && <MenuPanel panel={panel} menu={menu} style={style} rtl={rtl} onItemClose={handleItemClose} />}
|
||||
</>
|
||||
{panel && <MenuPanel panel={panel} menu={menu} rtl={rtl} onItemClose={handleItemClose} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<>
|
||||
<MenuButton {...menu} {...props}>
|
||||
<box>{children}</box>
|
||||
return (
|
||||
<div className={s(style, { menuTrigger: true })}>
|
||||
<MenuButton className={s(style, { menuButton: true })} {...menu} {...props}>
|
||||
<div className={s(style, { box: true })}>{children}</div>
|
||||
</MenuButton>
|
||||
{panel && <MenuPanel panel={panel} menu={menu} style={style} rtl={rtl} onItemClose={handleItemClose} />}
|
||||
</>,
|
||||
{panel && <MenuPanel panel={panel} menu={menu} rtl={rtl} onItemClose={handleItemClose} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -102,24 +101,22 @@ interface MenuPanelProps {
|
||||
panelAvailable?: boolean;
|
||||
onItemClose?: () => void;
|
||||
rtl?: boolean;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
const MenuPanel = observer<MenuPanelProps>(function MenuPanel({ panel, menu, panelAvailable, rtl, onItemClose, style }) {
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
|
||||
const MenuPanel = observer<MenuPanelProps>(function MenuPanel({ panel, menu, panelAvailable, rtl, onItemClose }) {
|
||||
const style = useS(MenuPanelItemAndTriggerStyles);
|
||||
if (!menu.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Menu {...menu} aria-label={panel.id} visible={panelAvailable ?? menu.visible}>
|
||||
<menu-box dir={rtl ? 'rtl' : undefined}>
|
||||
return (
|
||||
<Menu className={s(style, { menu: true })} {...menu} aria-label={panel.id} visible={panelAvailable ?? menu.visible}>
|
||||
<div className={s(style, { menuBox: true })} dir={rtl ? 'rtl' : undefined}>
|
||||
{panel.menuItems.map(item => (
|
||||
<MenuPanelElement key={item.id} item={item} menu={menu} style={style} onItemClose={onItemClose} />
|
||||
<MenuPanelElement key={item.id} item={item} menu={menu} onItemClose={onItemClose} />
|
||||
))}
|
||||
</menu-box>
|
||||
</Menu>,
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -131,11 +128,10 @@ interface IMenuPanelElementProps extends Omit<React.ButtonHTMLAttributes<any>, '
|
||||
item: IMenuItem;
|
||||
menu: MenuStateReturn; // from reakit useMenuState
|
||||
onItemClose?: () => void;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
const MenuPanelElement = observer<IMenuPanelElementProps>(function MenuPanelElement({ item, menu, onItemClose, style }) {
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
const MenuPanelElement = observer<IMenuPanelElementProps>(function MenuPanelElement({ item, menu, onItemClose }) {
|
||||
const style = useS(MenuPanelItemAndTriggerStyles);
|
||||
const onClick = useCallback(() => {
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
@@ -152,26 +148,25 @@ const MenuPanelElement = observer<IMenuPanelElementProps>(function MenuPanelElem
|
||||
}
|
||||
|
||||
if (item.panel) {
|
||||
return styled(styles)(
|
||||
return (
|
||||
<MenuItem
|
||||
{...menu}
|
||||
{...use({ hidden: item.isHidden })}
|
||||
className={s(style, { menuItem: true, hidden: item.isHidden })}
|
||||
aria-label={item.id}
|
||||
disabled={item.isDisabled}
|
||||
menuItem={item}
|
||||
style={style}
|
||||
onItemClose={onItemClose}
|
||||
onClick={onClick}
|
||||
{...{ as: MenuInnerTrigger }}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'radio') {
|
||||
return styled(styles)(
|
||||
return (
|
||||
<MenuItemRadio
|
||||
{...menu}
|
||||
{...use({ hidden: item.isHidden })}
|
||||
className={s(style, { menuItemRadio: true, hidden: item.isHidden })}
|
||||
aria-label={item.id}
|
||||
disabled={item.isDisabled}
|
||||
name={item.id}
|
||||
@@ -179,16 +174,16 @@ const MenuPanelElement = observer<IMenuPanelElementProps>(function MenuPanelElem
|
||||
checked={item.isChecked}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MenuPanelItem menuItem={item} style={style} />
|
||||
</MenuItemRadio>,
|
||||
<MenuPanelItem className={s(style, { menuPanelItem: true })} menuItem={item} />
|
||||
</MenuItemRadio>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'checkbox') {
|
||||
return styled(styles)(
|
||||
return (
|
||||
<MenuItemCheckbox
|
||||
{...menu}
|
||||
{...use({ hidden: item.isHidden })}
|
||||
className={s(style, { menuItemCheckbox: true, hidden: item.isHidden })}
|
||||
aria-label={item.id}
|
||||
disabled={item.isDisabled}
|
||||
name={item.id}
|
||||
@@ -196,15 +191,21 @@ const MenuPanelElement = observer<IMenuPanelElementProps>(function MenuPanelElem
|
||||
checked={item.isChecked}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MenuPanelItem menuItem={item} style={style} />
|
||||
</MenuItemCheckbox>,
|
||||
<MenuPanelItem className={s(style, { menuPanelItem: true })} menuItem={item} />
|
||||
</MenuItemCheckbox>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<MenuItem {...menu} {...use({ hidden: item.isHidden })} aria-label={item.id} disabled={item.isDisabled} onClick={onClick}>
|
||||
<MenuPanelItem menuItem={item} style={style} />
|
||||
</MenuItem>,
|
||||
return (
|
||||
<MenuItem
|
||||
className={s(style, { menuItem: true, hidden: item.isHidden })}
|
||||
{...menu}
|
||||
aria-label={item.id}
|
||||
disabled={item.isDisabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MenuPanelItem className={s(style, { menuPanelItem: true })} menuItem={item} />
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -215,13 +216,13 @@ const MenuPanelElement = observer<IMenuPanelElementProps>(function MenuPanelElem
|
||||
interface IMenuInnerTriggerProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
|
||||
menuItem: IMenuItem;
|
||||
onItemClose?: () => void;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export const MenuInnerTrigger = observer<IMenuInnerTriggerProps, HTMLButtonElement>(
|
||||
forwardRef(function MenuInnerTrigger(props, ref) {
|
||||
const { menuItem, style, onItemClose, ...rest } = props;
|
||||
const { menuItem, onItemClose, ...rest } = props;
|
||||
const menu = useMenuState();
|
||||
const style = useS(MenuPanelItemAndTriggerStyles);
|
||||
|
||||
const handleItemClose = useCallback(() => {
|
||||
menu.hide();
|
||||
@@ -232,17 +233,17 @@ export const MenuInnerTrigger = observer<IMenuInnerTriggerProps, HTMLButtonEleme
|
||||
menuItem.onMouseEnter?.();
|
||||
}, [menuItem.onMouseEnter]);
|
||||
|
||||
return styled(useStyles(menuPanelStyles, style))(
|
||||
return (
|
||||
<>
|
||||
<menu-panel-button-wrapper onMouseEnter={handleMouseEnter}>
|
||||
<MenuButton ref={ref} {...menu} {...rest}>
|
||||
<box>
|
||||
<MenuPanelItem menuItem={menuItem} style={style} />
|
||||
</box>
|
||||
<div className={s(style, { menuPanelButtonWrapper: true })} onMouseEnter={handleMouseEnter}>
|
||||
<MenuButton ref={ref} className={s(style, { menuButton: true })} {...menu} {...rest}>
|
||||
<div className={s(style, { box: true })}>
|
||||
<MenuPanelItem className={s(style, { menuPanelItem: true })} menuItem={menuItem} />
|
||||
</div>
|
||||
</MenuButton>
|
||||
</menu-panel-button-wrapper>
|
||||
<MenuPanel panel={menuItem.panel!} menu={menu} style={style} panelAvailable={menuItem.isPanelAvailable} onItemClose={handleItemClose} />
|
||||
</>,
|
||||
</div>
|
||||
<MenuPanel panel={menuItem.panel!} menu={menu} panelAvailable={menuItem.isPanelAvailable} onItemClose={handleItemClose} />
|
||||
</>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
MenuItemElement {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
menu-panel-item {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
outline: none;
|
||||
z-index: 999;
|
||||
}
|
||||
menu-box {
|
||||
composes: theme-typography--body2 theme-elevation-z5 from global;
|
||||
min-width: 140px;
|
||||
padding: 12px 0;
|
||||
|
||||
& menu-box {
|
||||
margin-top: -12px;
|
||||
}
|
||||
}
|
||||
menu-box,
|
||||
menu-panel-button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:global([aria-expanded='true']) {
|
||||
& Icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu-panel-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 0 4px;
|
||||
&[|separator] {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
& menu-item-text {
|
||||
display: block;
|
||||
padding: 0 4px;
|
||||
flex: 1;
|
||||
}
|
||||
& menu-item-content {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
& Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.5;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
& IconOrImage {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
& Loader {
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.menuBox {
|
||||
composes: theme-background-surface theme-text-on-surface from global;
|
||||
}
|
||||
|
||||
.menuItem,
|
||||
.menuItemCheckbox,
|
||||
.menuItemRadio {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
.menuItemElement {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
.menuPanelItem {
|
||||
composes: theme-border-color-background from global;
|
||||
}
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none !important;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu {
|
||||
outline: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.menuBox {
|
||||
composes: theme-typography--body2 theme-elevation-z5 from global;
|
||||
min-width: 140px;
|
||||
padding: 12px 0;
|
||||
& .menuBox {
|
||||
margin-top: -12px;
|
||||
}
|
||||
}
|
||||
.menuBox,
|
||||
.menuPanelButtonWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
&:hover,
|
||||
&:global([aria-expanded='true']) {
|
||||
& .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.menuPanelItem {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 0 4px;
|
||||
&.separator {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
& .menuItemText {
|
||||
display: block;
|
||||
padding: 0 4px;
|
||||
flex: 1;
|
||||
}
|
||||
& .menuItemContent {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
& .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.5;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
& .iconOrImage {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
& .loader {
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
.menuTrigger {
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export const SlideBox = observer<Props>(function SlideBox({ children, open, clas
|
||||
return (
|
||||
<div
|
||||
ref={divRef}
|
||||
className={s(slideBoxStyles, { slideBox: true}, s(slideBoxElementStyles, { open }), s(slideBoxOverlayStyles, { open }), className)}
|
||||
className={s(slideBoxStyles, { slideBox: true }, s(slideBoxElementStyles, { open }), s(slideBoxOverlayStyles, { open }), className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 slideBoxStyles = css`
|
||||
SlideBox {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
& SlideElement {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
white-space: normal;
|
||||
transition: transform cubic-bezier(0.4, 0, 0.2, 1) 0.6s;
|
||||
transform: translateX(-100%);
|
||||
|
||||
&:first-child {
|
||||
transition: width cubic-bezier(0.4, 0, 0.2, 1) 0.6s;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
& SlideOverlay {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 0;
|
||||
height: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgb(0 0 0 / 0%);
|
||||
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.6s;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
& SlideOverlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgb(0 0 0 / 35%);
|
||||
|
||||
& > :global(div) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
& SlideElement {
|
||||
transform: translateX(0%);
|
||||
|
||||
&:first-child {
|
||||
width: calc(100% - 120px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -47,6 +47,7 @@ export * from './Menu/MenuBarSmallItem';
|
||||
export * from './Menu/MenuEmptyItem';
|
||||
export * from './Menu/MenuItem';
|
||||
export { default as MenuItemStyles } from './Menu/MenuItem.m.css';
|
||||
export { default as MenuPanelItemAndTriggerStyles } from './MenuPanel/shared/MenuPanelItemAndTrigger.m.css';
|
||||
export * from './Menu/MenuItemCheckbox';
|
||||
export * from './Menu/MenuItemElement';
|
||||
export { default as MenuItemElementStyles } from './Menu/MenuItemElement.m.css';
|
||||
@@ -79,7 +80,6 @@ export * from './PropertiesTable/IProperty';
|
||||
export * from './Slide/SlideBox';
|
||||
export * from './Slide/SlideElement';
|
||||
export * from './Slide/SlideOverlay';
|
||||
export { default as SlideBoxStyles } from './Slide/SlideBox.m.css';
|
||||
|
||||
export * from './Split/SplitControls';
|
||||
export * from './Split/Pane';
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.iconOrImage {
|
||||
composes: theme-text-primary from global;
|
||||
}
|
||||
:global(.rdg-cell):not(:global([aria-selected='true'])):not(:hover) .cellMenu {
|
||||
display: none;
|
||||
}
|
||||
.cellMenu {
|
||||
flex: 0 0 auto;
|
||||
height: var(--rdg-row-height);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.menuTrigger {
|
||||
padding: 1px 2px;
|
||||
height: 100%;
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .icon {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
+21
-9
@@ -6,15 +6,14 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { Icon, MenuTrigger } from '@cloudbeaver/core-blocks';
|
||||
import { Icon, MenuPanelItemAndTriggerStyles, MenuTrigger, s, SContext, StyleRegistry, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import type { IDatabaseDataModel, IDataPresentationActions, IDataTableActions, IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridContextMenuService } from '../../DataGridContextMenu/DataGridContextMenuService';
|
||||
import { cellMenuStyles } from './cellMenuStyles';
|
||||
import styles from './CellMenu.m.css';
|
||||
|
||||
interface Props {
|
||||
model: IDatabaseDataModel;
|
||||
@@ -27,6 +26,16 @@ interface Props {
|
||||
onStateSwitch?: (state: boolean) => void;
|
||||
}
|
||||
|
||||
const registry: StyleRegistry = [
|
||||
[
|
||||
MenuPanelItemAndTriggerStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [styles],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export const CellMenu = observer<Props>(function CellMenu({
|
||||
model,
|
||||
actions,
|
||||
@@ -37,6 +46,7 @@ export const CellMenu = observer<Props>(function CellMenu({
|
||||
onClick,
|
||||
onStateSwitch,
|
||||
}) {
|
||||
const style = useS(styles);
|
||||
const dataGridContextMenuService = useService(DataGridContextMenuService);
|
||||
|
||||
const panel = dataGridContextMenuService.constructMenuWithContext(model, actions, spreadsheetActions, resultIndex, cellKey, simple);
|
||||
@@ -58,11 +68,13 @@ export const CellMenu = observer<Props>(function CellMenu({
|
||||
EventContext.set(event, EventStopPropagationFlag);
|
||||
}
|
||||
|
||||
return styled(cellMenuStyles)(
|
||||
<cell-menu as="div" onMouseUp={markStopPropagation} onDoubleClick={stopPropagation}>
|
||||
<MenuTrigger panel={panel} style={[cellMenuStyles]} modal onClick={handleClick} onVisibleSwitch={onStateSwitch}>
|
||||
<Icon name="snack" viewBox="0 0 16 10" />
|
||||
</MenuTrigger>
|
||||
</cell-menu>,
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<div className={s(style, { cellMenu: true })} onMouseUp={markStopPropagation} onDoubleClick={stopPropagation}>
|
||||
<MenuTrigger panel={panel} className={s(style, { menuTrigger: true })} modal onClick={handleClick} onVisibleSwitch={onStateSwitch}>
|
||||
<Icon className={s(style, { icon: true })} name="snack" viewBox="0 0 16 10" />
|
||||
</MenuTrigger>
|
||||
</div>
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 cellMenuStyles = css`
|
||||
IconOrImage {
|
||||
composes: theme-text-primary from global;
|
||||
}
|
||||
:global(.rdg-cell):not(:global([aria-selected='true'])):not(:hover) cell-menu {
|
||||
display: none;
|
||||
}
|
||||
cell-menu {
|
||||
flex: 0 0 auto;
|
||||
height: var(--rdg-row-height);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
MenuTrigger {
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& Icon {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.menu {
|
||||
composes: theme-text-on-surface from global;
|
||||
}
|
||||
.menuTrigger {
|
||||
composes: theme-ripple from global;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.toolsAction.hidden {
|
||||
display: none;
|
||||
}
|
||||
.menuTriggerIcon .iconOrImage {
|
||||
display: block;
|
||||
width: 16px;
|
||||
}
|
||||
.menuTriggerTitle {
|
||||
display: block;
|
||||
}
|
||||
.menuTriggerIcon + .menuTriggerTitle {
|
||||
padding-left: 8px;
|
||||
}
|
||||
+55
-60
@@ -7,80 +7,75 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { IconOrImage, MenuTrigger, ToolsAction, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import {
|
||||
IconOrImage,
|
||||
MenuPanelItemAndTriggerStyles,
|
||||
MenuTrigger,
|
||||
s,
|
||||
SContext,
|
||||
StyleRegistry,
|
||||
ToolsAction,
|
||||
useS,
|
||||
useTranslate,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import type { IMenuItem } from '@cloudbeaver/core-dialogs';
|
||||
|
||||
import styles from './TableFooterMenuItem.m.css';
|
||||
|
||||
type Props = ButtonHTMLAttributes<any> & {
|
||||
menuItem: IMenuItem;
|
||||
};
|
||||
|
||||
export const tableFooterMenuStyles = css`
|
||||
Menu {
|
||||
composes: theme-text-on-surface from global;
|
||||
}
|
||||
MenuTrigger {
|
||||
composes: theme-ripple from global;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&[|hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
ToolsAction[|hidden] {
|
||||
display: none;
|
||||
}
|
||||
menu-trigger-icon IconOrImage {
|
||||
display: block;
|
||||
width: 16px;
|
||||
}
|
||||
menu-trigger-title {
|
||||
display: block;
|
||||
}
|
||||
menu-trigger-icon + menu-trigger-title {
|
||||
padding-left: 8px;
|
||||
}
|
||||
`;
|
||||
const registry: StyleRegistry = [
|
||||
[
|
||||
MenuPanelItemAndTriggerStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [styles],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export const TableFooterMenuItem = observer<Props>(function TableFooterMenuItem({ menuItem, ...props }) {
|
||||
const translate = useTranslate();
|
||||
const style = useS(styles);
|
||||
|
||||
if (!menuItem.panel) {
|
||||
return styled(tableFooterMenuStyles)(
|
||||
<ToolsAction
|
||||
{...props}
|
||||
{...use({ hidden: menuItem.isHidden })}
|
||||
title={translate(menuItem.tooltip)}
|
||||
icon={menuItem.icon}
|
||||
viewBox="0 0 32 32"
|
||||
disabled={menuItem.isDisabled}
|
||||
onClick={() => menuItem.onClick?.()}
|
||||
>
|
||||
{translate(menuItem.title)}
|
||||
</ToolsAction>,
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<ToolsAction
|
||||
{...props}
|
||||
className={s(style, { toolsAction: true, hidden: menuItem.isHidden })}
|
||||
title={translate(menuItem.tooltip)}
|
||||
icon={menuItem.icon}
|
||||
viewBox="0 0 32 32"
|
||||
disabled={menuItem.isDisabled}
|
||||
onClick={() => menuItem.onClick?.()}
|
||||
>
|
||||
{translate(menuItem.title)}
|
||||
</ToolsAction>
|
||||
</SContext>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(tableFooterMenuStyles)(
|
||||
<MenuTrigger
|
||||
{...props}
|
||||
{...use({ hidden: menuItem.isHidden })}
|
||||
title={translate(menuItem.tooltip)}
|
||||
panel={menuItem.panel}
|
||||
disabled={menuItem.isDisabled}
|
||||
style={[tableFooterMenuStyles]}
|
||||
modal
|
||||
>
|
||||
{menuItem.icon && (
|
||||
<menu-trigger-icon>
|
||||
<IconOrImage icon={menuItem.icon} viewBox="0 0 32 32" />
|
||||
</menu-trigger-icon>
|
||||
)}
|
||||
{menuItem.title && <menu-trigger-title>{translate(menuItem.title)}</menu-trigger-title>}
|
||||
</MenuTrigger>,
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<MenuTrigger
|
||||
{...props}
|
||||
className={s(style, { menuTrigger: true, hidden: menuItem.isHidden })}
|
||||
title={translate(menuItem.tooltip)}
|
||||
panel={menuItem.panel}
|
||||
disabled={menuItem.isDisabled}
|
||||
modal
|
||||
>
|
||||
{menuItem.icon && (
|
||||
<div className={s(style, { menuTriggerIcon: true })}>
|
||||
<IconOrImage className={s(style, { iconOrImage: true })} icon={menuItem.icon} viewBox="0 0 32 32" />
|
||||
</div>
|
||||
)}
|
||||
{menuItem.title && <div className={s(style, { menuTriggerTitle: true })}>{translate(menuItem.title)}</div>}
|
||||
</MenuTrigger>
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
+14
-7
@@ -6,13 +6,12 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { getComputed, ToolsAction, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, ToolsAction, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import { tableFooterMenuStyles } from './TableFooterMenu/TableFooterMenuItem';
|
||||
import TableFooterMenuStyles from './TableFooterMenu/TableFooterMenuItem.m.css';
|
||||
import classes from './TableFooterRowCount.m.css';
|
||||
|
||||
interface Props {
|
||||
@@ -26,6 +25,7 @@ export const TotalCountAction = observer<Props>(function TotalCountAction({ onCl
|
||||
const result = model.getResult(resultIndex);
|
||||
const translate = useTranslate();
|
||||
const disabled = getComputed(() => model.isLoading() || model.isDisabled(resultIndex));
|
||||
const style = useS(TableFooterMenuStyles, classes);
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
@@ -34,11 +34,18 @@ export const TotalCountAction = observer<Props>(function TotalCountAction({ onCl
|
||||
const currentCount = result.loadedFully ? result.count : `${result.count}+`;
|
||||
const count = result.totalCount ?? currentCount;
|
||||
|
||||
return styled(tableFooterMenuStyles)(
|
||||
<div className={classes.wrapper} title={translate('data_viewer_total_count_tooltip')}>
|
||||
<ToolsAction disabled={disabled} loading={loading} icon="/icons/data_row_count.svg" viewBox="0 0 32 32" onClick={onClick}>
|
||||
return (
|
||||
<div className={s(style, { wrapper: true })} title={translate('data_viewer_total_count_tooltip')}>
|
||||
<ToolsAction
|
||||
className={s(style, { toolsAction: true })}
|
||||
disabled={disabled}
|
||||
loading={loading}
|
||||
icon="/icons/data_row_count.svg"
|
||||
viewBox="0 0 32 32"
|
||||
onClick={onClick}
|
||||
>
|
||||
<span>{count}</span>
|
||||
</ToolsAction>
|
||||
</div>,
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user