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:
sergeyteleshev
2024-04-29 21:22:57 +03:00
committed by GitHub
co-authored by mr-anton-t
parent 1f3ae6a508
commit c8457c2004
18 changed files with 535 additions and 568 deletions
@@ -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);
}
}
}
}
`;
+1 -1
View File
@@ -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';
@@ -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;
}
}
@@ -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>
);
});
@@ -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;
}
}
`;
@@ -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;
}
@@ -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>
);
});
@@ -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>
);
});