Revert "feat: CB-3517 css-modules for styling components (#1732)" (#1766)

This reverts commit f294ca7767.
This commit is contained in:
Alexey
2023-06-14 17:06:33 +03:00
committed by GitHub
parent 58728cf8ed
commit 9afb071539
200 changed files with 1742 additions and 1758 deletions
-1
View File
@@ -1,7 +1,6 @@
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.linkedEditing": true,
"task.autoDetect": "off",
@@ -1,3 +0,0 @@
.background {
fill: transparent;
}
@@ -7,9 +7,6 @@
*/
import { GlobalConstants, isValidUrl } from '@cloudbeaver/core-utils';
import { useS } from '../useS';
import style from './ConnectionImageWithMaskSvg.m.css';
interface Props {
icon?: string;
connected: boolean;
@@ -21,7 +18,6 @@ interface Props {
}
export const ConnectionImageWithMaskSvg: React.FC<Props> = ({ icon, connected, maskId, size = 16, markerRadius = 4, paddingSize = 0, className }) => {
const styles = useS(style);
if (!icon) {
return null;
}
@@ -52,7 +48,6 @@ export const ConnectionImageWithMaskSvg: React.FC<Props> = ({ icon, connected, m
mask={`url(#${maskId})`}
/>
</mask>
<rect className={styles.background} x="0" y="0" width={size} height={size} mask={connected ? `url(#${maskId})` : undefined} />
<image xlinkHref={url} width={size} height={size} mask={connected ? `url(#${maskId})` : undefined} />
</svg>
);
@@ -49,10 +49,6 @@ export const BASE_CONTAINERS_STYLES = css`
align-items: flex-start;
}
&[flexEnd] {
align-items: flex-end;
}
&[center] {
align-items: center;
justify-content: center;
@@ -68,7 +64,7 @@ export const BASE_CONTAINERS_STYLES = css`
}
&[parent] {
padding: 16px;
padding: 24px;
&[compact] {
padding: 16px;
@@ -80,14 +76,14 @@ export const BASE_CONTAINERS_STYLES = css`
}
&[gap] {
gap: 16px;
gap: 24px;
&[compact] {
gap: 16px;
}
&[dense] {
gap: 16px;
gap: 8px;
}
}
}
@@ -95,7 +91,7 @@ export const BASE_CONTAINERS_STYLES = css`
Group {
align-content: baseline;
box-sizing: border-box;
padding: 16px;
padding: 24px;
border-radius: var(--theme-group-element-radius);
&[dense] {
@@ -221,7 +217,7 @@ export const BASE_CONTAINERS_STYLES = css`
}
Group[box] > GroupTitle {
padding: 16px;
padding: 24px;
}
GroupSubTitle {
@@ -10,7 +10,6 @@ import type { ILayoutSizeProps } from './ILayoutSizeProps';
export interface IContainerProps extends ILayoutSizeProps {
hideEmpty?: boolean;
flexStart?: boolean;
flexEnd?: boolean;
baseline?: boolean;
center?: boolean;
vertical?: boolean;
@@ -9,7 +9,7 @@ import { filterLayoutFakeProps } from './filterLayoutFakeProps';
import type { IContainerProps } from './IContainerProps';
export function filterContainerFakeProps<T extends IContainerProps>(props: T): Omit<T, keyof IContainerProps> {
const { hideEmpty, flexStart, flexEnd, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...rest } =
const { hideEmpty, flexStart, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...rest } =
filterLayoutFakeProps(props);
return rest as Omit<T, keyof IContainerProps>;
@@ -29,7 +29,7 @@ const style = css`
error-inner-block {
display: flex;
margin: auto;
padding: 8px 16px;
padding: 16px 24px;
flex-direction: column;
align-items: center;
}
@@ -21,7 +21,7 @@ const style = css`
error {
flex: 1;
display: flex;
padding: 16px;
padding: 24px;
}
error-name {
@@ -30,7 +30,6 @@ const style = css`
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0;
& span {
display: inline-block;
@@ -149,7 +148,7 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({ exce
{!icon && (
<>
<error-data>
<error-name as="h2">
<error-name>
<span>{translate('core_blocks_exception_message_error_title')}</span>
</error-name>
<error-message>
@@ -32,7 +32,7 @@ export const BASE_DROPDOWN_STYLES = css`
outline: none;
color: inherit;
cursor: pointer;
gap: 16px;
gap: 8px;
& item-icon,
& item-title {
@@ -1,2 +0,0 @@
.checkbox {
}
@@ -10,10 +10,7 @@ import styled, { css } from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { s } from '../../s';
import { useS } from '../../useS';
import { useStyles } from '../../useStyles';
import CheckboxMarkupStyles from './CheckboxMarkup.m.css';
export type CheckboxMod = 'primary' | 'surface' | 'small';
@@ -98,8 +95,6 @@ interface ICheckboxMarkupProps extends Omit<React.InputHTMLAttributes<HTMLInputE
style?: ComponentStyle;
}
export { CheckboxMarkupStyles };
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({
id,
label,
@@ -112,7 +107,6 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
readOnly,
...rest
}) {
const styles = useS(CheckboxMarkupStyles);
const checkboxRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => {
@@ -131,7 +125,7 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
),
)(
<checkbox-container className={className} title={title}>
<checkbox className={s(styles, { checkbox: true })}>
<checkbox>
<checkbox-input ref={checkboxRef} as="input" type="checkbox" {...rest} disabled={rest.disabled || readOnly} id={id || rest.name} />
<checkbox-background>
<checkbox-checkmark as="svg" viewBox="0 0 24 24">
@@ -13,11 +13,13 @@ import { isControlPresented } from '../isControlPresented';
import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox';
const style = css`
Checkbox {
margin: -10px;
}
field {
display: flex;
align-items: center;
white-space: pre-wrap;
height: 32px;
& field-label {
cursor: pointer;
@@ -82,7 +82,7 @@ const styles = css`
outline: none;
color: inherit;
cursor: pointer;
gap: 16px;
gap: 8px;
& item-icon,
& item-title {
@@ -193,7 +193,7 @@ export const Combobox: ComboboxType = observer(function Combobox({
iconSelector,
titleSelector,
isDisabled,
onChange = () => {},
onChange = () => { },
onSelect,
onSwitch,
...rest
@@ -13,7 +13,7 @@
header {
flex-grow: 0;
flex-shrink: 0;
height: 32px;
height: 48px;
padding: 0 16px;
background-color: #2a7cb4;
display: flex;
@@ -21,8 +21,8 @@
}
.logo {
height: 24px;
width: 120px;
height: 34px;
width: 154px;
}
.content {
@@ -45,12 +45,16 @@
<div class="content">
<div class="message">
<p>Authorization was successful.</p>
<p>Now you can close this page.</p>
<p>
Authorization was successful.
</p>
<p>
Now you can close this page.
</p>
</div>
</div>
</div>
<script>
window.close();
</script>
</script>
+1 -1
View File
@@ -31,7 +31,7 @@ const styles = css`
IconOrImage {
width: 24px;
height: 24px;
margin-right: 8px;
margin-right: 16px;
}
`;
@@ -39,7 +39,7 @@ export const ITEM_LIST_STYLES = css`
composes: theme-background-surface theme-text-on-surface theme-typography--body1 from global;
position: sticky;
top: 0;
padding: 8px 16px;
padding: 16px 24px;
z-index: 1;
& input-box {
@@ -83,13 +83,13 @@ export const ITEM_LIST_STYLES = css`
display: flex;
box-sizing: border-box;
align-items: center;
padding: 0 8px;
padding: 0 16px;
}
list-item-icon {
display: flex;
align-items: center;
box-sizing: border-box;
padding: 4px;
padding: 8px;
& StaticImage {
box-sizing: border-box;
@@ -101,7 +101,7 @@ export const ITEM_LIST_STYLES = css`
box-sizing: border-box;
font-weight: 500;
min-width: 250px;
padding: 4px 16px 4px 8px;
padding: 8px 24px 8px 8px;
border-right: 1px solid;
white-space: nowrap;
@@ -112,7 +112,7 @@ export const ITEM_LIST_STYLES = css`
composes: theme-typography--body2 from global;
box-sizing: border-box;
max-width: 460px;
padding: 4px 8px 4px 16px;
padding: 8px 8px 8px 24px;
white-space: nowrap;
overflow: hidden;
@@ -1,28 +0,0 @@
.menuButton {
composes: theme-ripple from global;
}
.menuButton {
background: none;
border: none;
outline: none !important;
color: inherit;
cursor: pointer;
& .box {
display: flex;
align-items: center;
flex: 1;
height: inherit;
}
}
.menuButtonLink {
position: absolute;
width: 0;
height: 0;
overflow: hidden;
box-sizing: border-box;
padding: 0;
margin: 0;
border: none;
background: transparent;
}
+20 -34
View File
@@ -8,22 +8,25 @@
import { observer } from 'mobx-react-lite';
import React, { forwardRef, useLayoutEffect, useRef, useState } from 'react';
import { MenuButton, MenuInitialState, useMenuState } from 'reakit/Menu';
import styled from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { ErrorBoundary } from '../ErrorBoundary';
import { s } from '../s';
import { useCombinedRef } from '../useCombinedRef';
import { useObjectRef } from '../useObjectRef';
import { useS } from '../useS';
import style from './Menu.m.css';
import { useStyles } from '../useStyles';
import { MenuPanel } from './MenuPanel';
import { menuPanelStyles } from './menuPanelStyles';
import { IMenuState, MenuStateContext } from './MenuStateContext';
import type { IMouseContextMenu } from './useMouseContextMenu';
interface IMenuProps extends React.ButtonHTMLAttributes<any> {
interface IMenuProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
mouseContextMenu?: IMouseContextMenu;
label: string;
items: React.ReactNode | (() => React.ReactNode);
menuRef?: React.RefObject<IMenuState | undefined>;
style?: ComponentStyle;
disclosure?: boolean;
placement?: MenuInitialState['placement'];
submenu?: boolean;
@@ -45,6 +48,7 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(
menuRef,
disclosure,
children,
style,
placement,
visible,
hasBindings,
@@ -54,7 +58,6 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(
modal,
submenu,
rtl,
className,
...props
},
ref,
@@ -71,7 +74,7 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(
visible,
rtl,
});
const styles = useS(style);
const styles = useStyles(menuPanelStyles, style);
if (menuRef) {
//@ts-expect-error Ref mutation
@@ -127,24 +130,17 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(
const MenuButtonLink = MenuButton;
if (React.isValidElement(children) && disclosure) {
return (
return styled(styles)(
<ErrorBoundary>
<MenuStateContext.Provider value={menu}>
<MenuButton
key={relativePosition ? 'link' : 'main'}
ref={combinedRef}
className={s(styles, { menuButton: true }, className)}
{...menu}
visible={menuVisible}
{...props}
{...children.props}
>
<MenuButton key={relativePosition ? 'link' : 'main'} ref={combinedRef} {...menu} visible={menuVisible} {...props} {...children.props}>
{disclosureProps => React.cloneElement(children, { ...disclosureProps, ...children.props })}
</MenuButton>
<MenuPanel
ref={menuPanelRef}
label={label}
menu={menu}
style={style}
rtl={rtl}
submenu={submenu}
panelAvailable={panelAvailable}
@@ -153,31 +149,23 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(
>
{items}
</MenuPanel>
{relativePosition && (
<MenuButtonLink ref={menuButtonLinkRef} className={s(styles, { menuButtonLink: true })} {...menu} visible={menuVisible} />
)}
{relativePosition && <MenuButtonLink ref={menuButtonLinkRef} {...menu} visible={menuVisible} />}
</MenuStateContext.Provider>
</ErrorBoundary>
</ErrorBoundary>,
);
}
return (
return styled(styles)(
<ErrorBoundary>
<MenuStateContext.Provider value={menu}>
<MenuButton
key={relativePosition ? 'link' : 'main'}
ref={combinedRef}
className={s(styles, { menuButton: true }, className)}
{...menu}
visible={menuVisible}
{...props}
>
<div className={s(styles, { box: true }, className)}>{children}</div>
<MenuButton key={relativePosition ? 'link' : 'main'} ref={combinedRef} {...menu} visible={menuVisible} {...props}>
<box>{children}</box>
</MenuButton>
<MenuPanel
ref={menuPanelRef}
label={label}
menu={menu}
style={style}
rtl={rtl}
submenu={submenu}
panelAvailable={panelAvailable}
@@ -186,11 +174,9 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(
>
{items}
</MenuPanel>
{relativePosition && (
<MenuButtonLink ref={menuButtonLinkRef} className={s(styles, { menuButtonLink: true })} {...menu} visible={menuVisible} />
)}
{relativePosition && <MenuButtonLink ref={menuButtonLinkRef} {...menu} visible={menuVisible} />}
</MenuStateContext.Provider>
</ErrorBoundary>
</ErrorBoundary>,
);
}),
);
@@ -6,19 +6,29 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { useTranslate } from '../localization/useTranslate';
import { useStyles } from '../useStyles';
import { MenuItem } from './MenuItem';
import { MenuItemElement } from './MenuItemElement';
import { menuPanelStyles } from './menuPanelStyles';
export const MenuEmptyItem = observer(function MenuEmptyItem() {
interface Props extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
style?: ComponentStyle;
}
export const MenuEmptyItem = observer<Props>(function MenuEmptyItem({ style }) {
const styles = useStyles(menuPanelStyles, style);
const translate = useTranslate();
const label = translate('core_ui_empty');
return (
<MenuItem id="empty" aria-label={label} disabled>
<MenuItemElement label={label} />
</MenuItem>
return styled(styles)(
<MenuItem id="empty" label={label} disabled>
<MenuItemElement label={label} style={style} />
</MenuItem>,
);
});
@@ -1,23 +0,0 @@
.menuItem {
composes: theme-ripple from global;
composes: menuItem from './MenuItemElement.m.css';
display: flex;
align-items: center;
border: none;
padding: 0;
background: none;
text-align: left;
outline: none;
color: inherit;
cursor: pointer;
white-space: nowrap;
&.hidden {
display: none;
}
&:global([aria-selected='true']) {
opacity: 0.7;
}
}
@@ -8,44 +8,46 @@
import { observer } from 'mobx-react-lite';
import React, { useContext } from 'react';
import { MenuItem as ReakitMenuItem } from 'reakit/Menu';
import type { CompositeItemOptions } from 'reakit/ts';
import styled, { use } from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { s } from '../s';
import { useCombinedHandler } from '../useCombinedHandler';
import { useS } from '../useS';
import style from './MenuItem.m.css';
import { useStyles } from '../useStyles';
import { menuPanelStyles } from './menuPanelStyles';
import { MenuStateContext } from './MenuStateContext';
import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent';
export type MenuItemOptions = CompositeItemOptions & {
export interface IMenuItemProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
label: string;
hidden?: boolean;
selected?: boolean;
close?: boolean;
};
style?: ComponentStyle;
}
export const MenuItem: ReakitProxyComponent<'button', MenuItemOptions> = observer<ReakitProxyComponentOptions<'button', MenuItemOptions>>(
function MenuItem({ children, hidden, selected, close, onClick, className, ...rest }) {
const menu = useContext(MenuStateContext);
const styles = useS(style);
export const MenuItem = observer<IMenuItemProps>(function MenuItem({ label, children, hidden, selected, close, style, onClick, ...rest }) {
const menu = useContext(MenuStateContext);
const styles = useStyles(menuPanelStyles, style);
const handleClick = useCombinedHandler<[React.MouseEvent<HTMLButtonElement>]>(onClick, function handleClick() {
if (close) {
menu?.hide();
}
});
const handleClick = useCombinedHandler<[React.MouseEvent<HTMLButtonElement>]>(onClick, function handleClick() {
if (close) {
menu?.hide();
}
});
const MenuItem = ReakitMenuItem;
const MenuItem = ReakitMenuItem;
return (
<MenuItem
{...menu}
aria-selected={selected}
{...rest}
className={s(styles, { menuItem: true, hidden }, className)}
disabled={selected || rest.disabled}
onClick={handleClick}
>
{children}
</MenuItem>
);
},
);
return styled(styles)(
<MenuItem
{...menu}
{...use({ hidden: hidden })}
aria-label={label}
aria-selected={selected}
{...rest}
disabled={selected || rest.disabled}
onClick={handleClick}
>
{children}
</MenuItem>,
);
});
@@ -1,34 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { MenuItemOptions, MenuItemCheckbox as ReakitMenuItemCheckbox } from 'reakit/Menu';
import type { CheckboxOptions } from 'reakit/ts';
import { s } from '../s';
import { useS } from '../useS';
import style from './MenuItem.m.css';
import { MenuStateContext } from './MenuStateContext';
import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent';
type Options = CheckboxOptions & MenuItemOptions;
export const MenuItemCheckbox: ReakitProxyComponent<'button', Options> = observer<ReakitProxyComponentOptions<'button', Options>>(
function MenuItemCheckbox({ hidden, children, className, ...rest }) {
const menu = useContext(MenuStateContext);
const styles = useS(style);
const MenuItemCheckbox = ReakitMenuItemCheckbox;
return (
<MenuItemCheckbox {...menu} className={s(styles, { menuItem: true, hidden }, className)} {...rest}>
{children}
</MenuItemCheckbox>
);
},
);
@@ -1,70 +0,0 @@
.menuPanelItem {
composes: theme-ripple theme-border-color-background from global;
flex: 1;
display: flex;
position: relative;
align-items: center;
height: 26px;
padding: 0 4px;
& .menuItemText,
& .menuItemBinding {
display: block;
padding: 0 4px;
}
& .menuItemBinding {
flex: 1;
text-align: right;
padding-left: 32px;
width: 80px;
overflow: hidden;
text-overflow: ellipsis;
}
& .menuItemIcon,
& .menuItemContent {
width: 24px;
height: 24px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
& .menuItemContent .icon {
width: 10px;
height: 10px;
opacity: 0.5;
}
& .iconOrImage {
width: 16px;
height: 16px;
object-fit: contain;
}
& .loader {
width: 16px;
}
}
.menuItemBinding {
composes: theme-typography--caption from global;
}
.menuBox:not([data-s-has-bindings='true']) .menuItemBinding {
width: 0;
}
.menuBox[dir='rtl'] {
& .menuItemText {
text-align: right;
}
& .menuItemContent .icon {
transform: rotate(180deg);
}
}
.menuItem:hover,
.menuItem:global([aria-expanded='true']) {
& .menuItemContent .icon {
opacity: 1;
}
}
@@ -6,15 +6,17 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { Icon } from '../Icon';
import { IconOrImage } from '../IconOrImage';
import { Loader } from '../Loader/Loader';
import { useTranslate } from '../localization/useTranslate';
import { s } from '../s';
import { useS } from '../useS';
import { useStateDelay } from '../useStateDelay';
import style from './MenuItemElement.m.css';
import { useStyles } from '../useStyles';
import { menuPanelStyles } from './menuPanelStyles';
interface IMenuItemElementProps {
label: string;
@@ -26,6 +28,7 @@ interface IMenuItemElementProps {
menu?: boolean;
loading?: boolean;
panelAvailable?: boolean;
style?: ComponentStyle;
}
export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItemElement({
@@ -37,34 +40,22 @@ export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItem
menu,
panelAvailable,
loading = false,
style = [],
}) {
const styles = useS(style);
const translate = useTranslate();
const title = translate(label);
loading = useStateDelay(loading, 100);
return (
<div className={s(styles, { menuPanelItem: true })} title={tooltip ? translate(tooltip) : title}>
<div className={s(styles, { menuItemIcon: true })}>
<Loader className={s(styles, { loader: true })} suspense small fullSize>
{typeof icon === 'string' ? <IconOrImage className={s(styles, { iconOrImage: true })} icon={icon} /> : icon}
</Loader>
</div>
{displayLabel ? (
<div className={s(styles, { menuItemText: true })} title={title}>
{title}
</div>
) : (
<div />
)}
<div className={s(styles, { menuItemBinding: true })} title={binding}>
{binding}
</div>
<div className={s(styles, { menuItemContent: true })}>
{loading && <Loader className={s(styles, { loader: true })} small fullSize />}
{panelAvailable !== false && menu && !loading && <Icon name="context-menu-submenu" viewBox="0 0 6 7" className={s(styles, { icon: true })} />}
</div>
</div>
return styled(useStyles(menuPanelStyles, style))(
<menu-panel-item title={tooltip ? translate(tooltip) : title}>
<menu-item-icon>{typeof icon === 'string' ? <IconOrImage icon={icon} /> : icon}</menu-item-icon>
{displayLabel ? <menu-item-text title={title}>{title}</menu-item-text> : <padding />}
<menu-item-binding title={binding}>{binding}</menu-item-binding>
<menu-item-content>
{loading && <Loader small fullSize />}
{panelAvailable !== false && menu && !loading && <Icon name="context-menu-submenu" viewBox="0 0 6 7" />}
</menu-item-content>
</menu-panel-item>,
);
});
@@ -1,34 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { MenuItemOptions, MenuItemRadio as ReakitMenuItemRadio } from 'reakit/Menu';
import type { RadioOptions } from 'reakit/ts';
import { s } from '../s';
import { useS } from '../useS';
import style from './MenuItem.m.css';
import { MenuStateContext } from './MenuStateContext';
import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent';
type Options = RadioOptions & MenuItemOptions;
export const MenuItemRadio: ReakitProxyComponent<'button', Options> = observer<ReakitProxyComponentOptions<'button', Options>>(
function MenuItemRadio({ hidden, children, className, ...rest }) {
const menu = useContext(MenuStateContext);
const styles = useS(style);
const MenuItemRadio = ReakitMenuItemRadio;
return (
<MenuItemRadio {...menu} className={s(styles, { menuItem: true, hidden }, className)} {...rest}>
{children}
</MenuItemRadio>
);
},
);
@@ -1,27 +0,0 @@
.menu {
outline: none;
z-index: 999;
}
.menuBox {
composes: theme-background-surface theme-text-on-surface theme-group-element-radius theme-typography--caption theme-elevation-z5 from global;
composes: menuBox from './MenuItemElement.m.css';
display: flex;
flex-direction: column;
min-width: 140px;
padding: 6px 0;
& .menuBox {
margin-top: -6px;
}
}
.menu.modal .menuBox {
overflow: auto;
max-height: 80vh;
overflow-x: hidden;
}
.menu.submenu {
margin-top: -6px;
}
@@ -8,13 +8,15 @@
import { observer } from 'mobx-react-lite';
import { Children, forwardRef } from 'react';
import { Menu, MenuStateReturn } from 'reakit/Menu';
import styled, { use } from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { ErrorBoundary } from '../ErrorBoundary';
import { getComputed } from '../getComputed';
import { s } from '../s';
import { useS } from '../useS';
import { useStyles } from '../useStyles';
import { MenuEmptyItem } from './MenuEmptyItem';
import style from './MenuPanel.m.css';
import { menuPanelStyles } from './menuPanelStyles';
export interface IMenuPanelProps {
label: string;
@@ -25,12 +27,13 @@ export interface IMenuPanelProps {
children: React.ReactNode | (() => React.ReactNode);
rtl?: boolean;
submenu?: boolean;
style?: ComponentStyle;
className?: string;
}
export const MenuPanel = observer<IMenuPanelProps, HTMLDivElement>(
forwardRef(function MenuPanel({ label, menu, submenu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, className }, ref) {
const styles = useS(style);
forwardRef(function MenuPanel({ label, menu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, style, className }, ref) {
const styles = useStyles(menuPanelStyles, style);
const visible = menu.visible;
if (!visible) {
@@ -45,21 +48,15 @@ export const MenuPanel = observer<IMenuPanelProps, HTMLDivElement>(
renderedChildren = typeof children === 'function' ? children() : children;
}
return (
return styled(styles)(
<ErrorBoundary>
<Menu
ref={ref}
className={s(styles, { menu: true, modal: menu.modal, submenu }, className)}
{...menu}
aria-label={label}
visible={panelAvailable}
>
<div dir={rtl ? 'rtl' : undefined} data-s-has-bindings={hasBindings} className={s(styles, { menuBox: true })}>
{Children.count(renderedChildren) === 0 && <MenuEmptyItem />}
<Menu ref={ref} {...menu} aria-label={label} className={className} visible={panelAvailable}>
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
{Children.count(renderedChildren) === 0 && <MenuEmptyItem style={style} />}
{renderedChildren}
</div>
</menu-box>
</Menu>
</ErrorBoundary>
</ErrorBoundary>,
);
}),
);
@@ -1,11 +0,0 @@
.menuSeparator {
flex: 1;
width: 100%;
display: flex;
margin: 4px 0;
&:first-child,
&:last-child {
display: none;
}
}
@@ -1,19 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { MenuSeparator as MenuSeparatorBase } from 'reakit/Menu';
import type { SeparatorOptions } from 'reakit/ts';
import { s } from '../s';
import { useS } from '../useS';
import style from './MenuSeparator.m.css';
import type { ReakitProxyComponentOptions } from './ReakitProxyComponent';
export const MenuSeparator = function MenuSeparator({ className, ...props }: ReakitProxyComponentOptions<'hr', SeparatorOptions>) {
const styles = useS(style);
return <MenuSeparatorBase {...props} className={s(styles, { menuSeparator: true }, className)} />;
};
@@ -1,25 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { ReactElement } from 'react';
import type { As, PropsWithAs } from 'reakit-utils/types';
export type ReakitProxyPropsWithAs<O, T extends As> = Omit<PropsWithAs<O, T>, 'children'> & {
children?: React.ReactNode;
};
export type ReakitProxyComponentOptions<T extends As, O> = ReakitProxyPropsWithAs<O, T>;
export type ReakitProxyComponent<T extends As, O> = {
<TT extends As = T>(
props: ReakitProxyPropsWithAs<O, TT> & {
as: TT;
},
context?: any,
): ReactElement<any, any> | null;
(props: ReakitProxyPropsWithAs<O, T>, context?: any): ReactElement<any, any> | null;
};
@@ -0,0 +1,186 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const menuPanelStyles = css`
menu-box {
composes: theme-background-surface theme-text-on-surface from global;
}
MenuItem,
MenuItemCheckbox,
MenuItemRadio,
MenuButton,
MenuItemElement {
composes: theme-ripple from global;
}
menu-panel-item,
MenuSeparator {
composes: theme-border-color-background from global;
}
MenuButton {
background: none;
border: none;
outline: none !important;
color: inherit;
cursor: pointer;
& box {
display: flex;
align-items: center;
flex: 1;
height: inherit;
}
}
MenuButtonLink {
position: absolute;
width: 0;
height: 0;
overflow: hidden;
box-sizing: border-box;
padding: 0;
margin: 0;
border: none;
background: transparent;
}
Menu {
outline: none;
z-index: 999;
}
MenuPanel[submenu] {
margin-top: -6px;
}
menu-box {
composes: theme-group-element-radius theme-typography--body2 theme-elevation-z5 from global;
min-width: 140px;
padding: 6px 0;
& menu-box {
margin-top: -6px;
}
}
menu-box {
display: flex;
flex-direction: column;
&:not([|hasBindings]) menu-item-binding {
width: 0;
}
}
Menu[modal] menu-box {
overflow: auto;
max-height: 80vh;
overflow-x: hidden;
}
MenuItem,
MenuItemCheckbox,
MenuItemRadio {
display: flex;
align-items: center;
border: none;
padding: 0;
background: none;
text-align: left;
outline: none;
color: inherit;
cursor: pointer;
white-space: nowrap;
&[use|hidden] {
display: none;
}
&:global([aria-selected='true']) {
opacity: 0.7;
}
&:hover,
&:global([aria-expanded='true']) {
& Icon {
opacity: 1;
}
}
}
MenuSeparator {
flex: 1;
width: 100%;
display: flex;
margin: 6px 0;
border: none !important;
border-bottom: 1px solid !important;
&:first-child,
&:last-child {
display: none;
}
}
menu-item-binding {
composes: theme-typography--caption from global;
}
menu-panel-item {
flex: 1;
display: flex;
position: relative;
align-items: center;
height: 30px;
padding: 0 4px;
& menu-item-text,
& menu-item-binding {
display: block;
padding: 0 4px;
}
& menu-item-binding {
flex: 1;
text-align: right;
padding-left: 32px;
width: 80px;
overflow: hidden;
text-overflow: ellipsis;
}
& menu-item-icon,
& menu-item-content {
width: 24px;
height: 24px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
& menu-item-content Icon {
width: 10px;
height: 10px;
}
& Icon {
width: 16px;
height: 16px;
opacity: 0.5;
}
& IconOrImage {
width: 16px;
height: 16px;
object-fit: contain;
}
& Loader {
width: 16px;
}
}
Menu[rtl] {
& menu-item-text {
text-align: right;
}
& menu-panel-item menu-item-content Icon {
transform: rotate(180deg);
}
}
`;
@@ -60,7 +60,7 @@ export const OVERLAY_BASE_STYLES = css`
message,
actions {
flex-shrink: 0;
padding: 16px;
padding: 24px;
}
message {
@@ -37,7 +37,7 @@ const styles = css`
align-items: center;
box-sizing: border-box;
flex: 1;
padding: 2px 0;
padding: 4px 0;
& ShadowInput {
height: 24px;
@@ -1,31 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import React, { createContext } from 'react';
import type { IStyleRegistry, Style } from '@cloudbeaver/core-theming';
type Registry = Map<Style, IStyleRegistry[]>;
export type StyleRegistry = [Style, IStyleRegistry][];
export const SContextReact = createContext<Registry>(new Map());
type Props = {
registry: StyleRegistry;
};
export const SContext: React.FC<React.PropsWithChildren<Props>> = function SContext({ registry, children }) {
const context = React.useContext(SContextReact);
const state = new Map(context.entries());
for (const [style, styles] of registry) {
state.set(style, [...(state.get(style) || []), styles]);
}
return <SContextReact.Provider value={state}>{children}</SContextReact.Provider>;
};
@@ -1,123 +0,0 @@
.container {
position: absolute;
display: flex;
justify-content: center;
align-content: center;
align-items: center;
/* pointer outline */
&::before {
content: '';
position: absolute;
background: transparent;
}
}
.button {
composes: theme-button theme-button_unelevated theme-button_background theme-elevation-z3 from global;
opacity: 0;
transition-property: opacity, box-shadow !important;
transition: opacity 0.3s ease-in-out;
}
.ripple {
composes: theme-button_ripple from global;
}
.container:hover .button {
opacity: 1;
}
.container[data-s-split='vertical'] {
flex-direction: column;
cursor: ew-resize;
height: 100%;
width: 5px;
left: -2px;
&[data-s-mode='minimize'] {
left: 0;
}
&[data-s-mode='maximize'] {
left: -2px;
}
& .button {
margin: 8px 0;
min-width: 16px;
&::after {
transform: rotate(180deg);
}
}
&[data-s-mode='maximize'] .button,
&[data-s-mode='minimize'] .button {
min-height: 64px;
}
& .button.primary::after {
transform: rotate(0deg);
}
}
.container[data-s-split='vertical'].inverse {
flex-direction: column-reverse;
}
.container[data-s-split='horizontal'] {
flex-direction: row;
cursor: ns-resize;
width: 100%;
height: 5px;
top: -2px;
&[data-s-mode='minimize'] {
top: 0;
}
&[data-s-mode='maximize'] {
top: -2px;
}
& .button {
margin: 0 8px;
min-width: 28px;
height: 16px;
&::after {
transform: rotate(270deg);
}
}
&[data-s-mode='maximize'] .button,
&[data-s-mode='minimize'] .button {
min-width: 64px;
}
& .button.primary::after {
transform: rotate(90deg);
}
}
.container[data-s-split='horizontal'].inverse {
flex-direction: row-reverse;
}
.button {
padding: 0 !important;
&::after {
position: absolute;
top: 50%;
left: 50%;
content: '';
display: block;
width: 0;
height: 0;
border-style: solid;
border-width: 3px 4px 3px 0;
border-color: transparent #000 transparent transparent;
margin: -3px -2px;
}
}
@@ -5,15 +5,14 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { s } from '../s';
import styled, { use } from 'reshadow';
import { useObjectRef } from '../useObjectRef';
import { useS } from '../useS';
import SplitControlsStyles from './SplitControls.m.css';
import { buttonStyles } from './splitButtonStyles';
import { useSplit } from './useSplit';
export const SplitControls: React.FC = function SplitControls() {
const split = useSplit();
const styles = useS(SplitControlsStyles);
const inverse = split.state.isMainSecond();
@@ -52,27 +51,25 @@ export const SplitControls: React.FC = function SplitControls() {
['handleCollapse', 'handleExpand'],
);
return (
<div
data-s-split={split.state.split}
data-s-mode={inverseMode}
className={s(styles, { container: true, inverse })}
return styled(buttonStyles)(
<container
onMouseDown={split.state.onMouseDown}
onTouchStart={split.state.onTouchStart}
onTouchEnd={split.state.onTouchEnd}
onClick={split.state.onClick}
onDoubleClick={split.state.onDoubleClick}
{...use({ split: split.state.split, inverse, mode: inverseMode })}
>
{split.state.mode !== 'minimize' && (
<button className={s(styles, { button: true, primary: !inverse })} type="button" onClick={handlers.handleCollapse}>
<div className={s(styles, { ripple: true })} />
<button type="button" {...use({ isPrimary: !inverse })} onClick={handlers.handleCollapse}>
<ripple />
</button>
)}
{split.state.mode !== 'maximize' && (
<button className={s(styles, { button: true, primary: inverse })} type="button" onClick={handlers.handleExpand}>
<div className={s(styles, { ripple: true })} />
<button type="button" {...use({ isPrimary: inverse })} onClick={handlers.handleExpand}>
<ripple />
</button>
)}
</div>
</container>,
);
};
@@ -0,0 +1,131 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const buttonStyles = css`
container {
position: absolute;
display: flex;
justify-content: center;
align-content: center;
align-items: center;
/* pointer outline */
&::before {
content: '';
position: absolute;
background: transparent;
}
}
button {
composes: theme-button theme-button_unelevated theme-button_background theme-elevation-z3 from global;
opacity: 0;
transition-property: opacity, box-shadow !important;
transition: opacity 0.3s ease-in-out;
}
ripple {
composes: theme-button_ripple from global;
}
container:hover button {
opacity: 1;
}
container[|split='vertical'] {
flex-direction: column;
cursor: ew-resize;
height: 100%;
width: 5px;
left: -2px;
&[|mode='minimize'] {
left: 0;
}
&[|mode='maximize'] {
left: -2px;
}
& button {
margin: 8px 0;
min-width: 16px;
&::after {
transform: rotate(180deg);
}
}
&[|mode='maximize'] button,
&[|mode='minimize'] button {
min-height: 64px;
}
& button[|isPrimary]::after {
transform: rotate(0deg);
}
}
container[|split='vertical'][|inverse] {
flex-direction: column-reverse;
}
container[|split='horizontal'] {
flex-direction: row;
cursor: ns-resize;
width: 100%;
height: 5px;
top: -2px;
&[|mode='minimize'] {
top: 0;
}
&[|mode='maximize'] {
top: -2px;
}
& button {
margin: 0 8px;
min-width: 28px;
height: 16px;
&::after {
transform: rotate(270deg);
}
}
&[|mode='maximize'] button,
&[|mode='minimize'] button {
min-width: 64px;
}
& button[|isPrimary]::after {
transform: rotate(90deg);
}
}
container[|split='horizontal'][|inverse] {
flex-direction: row-reverse;
}
button {
padding: 0 !important;
&::after {
position: absolute;
top: 50%;
left: 50%;
content: '';
display: block;
width: 0;
height: 0;
border-style: solid;
border-width: 3px 4px 3px 0;
border-color: transparent #000 transparent transparent;
margin: -3px -2px;
}
}
`;
@@ -35,15 +35,15 @@ export const BASE_TABLE_STYLES = css`
&[|size='big'] {
& > thead > tr > th {
height: 36px;
height: 54px;
&[|min] {
width: 32px;
width: 46px;
}
}
& > tbody > tr > td {
height: 32px;
height: 46px;
}
}
@@ -83,7 +83,7 @@ export const BASE_TABLE_STYLES = css`
th {
box-sizing: border-box;
white-space: nowrap;
padding: 8px;
padding: 16px;
height: 36px;
padding-top: unset;
padding-bottom: unset;
@@ -114,7 +114,7 @@ export const BASE_TABLE_STYLES = css`
position: relative;
box-sizing: border-box;
height: 28px;
padding: 0 8px;
padding: 0 16px;
transition: padding ease-in-out 0.24s;
&[|ellipsis] {
@@ -26,8 +26,8 @@ interface Props {
const checkboxStyles = css`
Checkbox {
margin-left: -6px;
margin-right: -6px;
margin-left: -10px;
margin-right: -10px;
}
`;
@@ -23,8 +23,8 @@ interface Props {
const styles = css`
Checkbox {
margin-left: -6px;
margin-right: -6px;
margin-left: -10px;
margin-right: -10px;
}
`;
@@ -20,7 +20,7 @@ const style = css`
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 16px;
gap: 8px;
}
`;
@@ -9,7 +9,7 @@ import styled, { css } from 'reshadow';
const styles = css`
container {
height: 30px;
height: 32px;
display: flex;
flex-shrink: 0;
}
@@ -59,10 +59,6 @@ export const TREE_NODE_STYLES = css`
margin-right: 0;
margin-left: 4px;
}
& TreeNodeName {
margin-right: 4px;
}
}
}
@@ -99,7 +95,6 @@ export const TREE_NODE_STYLES = css`
padding-right: 0;
display: flex;
align-items: center;
margin-right: 4px;
}
TreeNodeFilter {
@@ -147,18 +142,21 @@ export const TREE_NODE_STYLES = css`
}
node TreeNodeControl[big] {
height: 32px;
padding: 0 8px;
height: 46px;
padding: 2 16px;
& TreeNodeSelect {
margin: 3px;
}
& TreeNodeName {
margin: 0;
padding-right: 4px;
padding-left: 4px;
padding-right: 16px;
padding-left: 16px;
}
& TreeNodeIcon,
& TreeNodeExpand {
width: 16px;
height: 16px;
width: 24px;
height: 24px;
}
& TreeNodeExpand [|size='small'] {
@@ -170,16 +168,12 @@ export const TREE_NODE_STYLES = css`
}
& > * {
margin-right: 4px;
margin-left: 4px;
}
& TreeNodeSelect {
margin: 0;
margin-right: 11px;
margin-left: 11px;
}
}
node TreeNodeControl[big] + TreeNodeNested {
padding-left: 16px;
padding-left: 46px;
}
`;
+1 -12
View File
@@ -23,23 +23,15 @@ export * from './localization/Translate';
export * from './localization/useTranslate';
export * from './ConnectionImageWithMask/ConnectionImageWithMask';
export { default as ConnectionImageWithMaskSvgStyles } from './ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css';
export * from './Menu/ACTION_ICON_BUTTON_STYLES';
export * from './Menu/Menu';
export { default as MenuStyles } from './Menu/Menu.m.css';
export * from './Menu/MenuBarSmallItem';
export * from './Menu/MenuEmptyItem';
export * from './Menu/MenuItem';
export { default as MenuItemStyles } from './Menu/MenuItem.m.css';
export * from './Menu/MenuItemCheckbox';
export * from './Menu/MenuItemElement';
export { default as MenuItemElementStyles } from './Menu/MenuItemElement.m.css';
export * from './Menu/MenuItemRadio';
export * from './Menu/MenuPanel';
export { default as MenuPanelStyles } from './Menu/MenuPanel.m.css';
export * from './Menu/MenuSeparator';
export { default as MenuSeparatorStyles } from './Menu/MenuSeparator.m.css';
export * from './Menu/menuPanelStyles';
export * from './Menu/MenuStateContext';
export * from './Menu/useMouseContextMenu';
@@ -164,8 +156,6 @@ export * from './ExceptionMessage';
export * from './getComputed';
export * from './IconButton';
export * from './IconOrImage';
export * from './s';
export * from './SContext';
export * from './StaticImage';
export * from './TextPlaceholder';
export * from './TimerIcon';
@@ -187,7 +177,6 @@ export * from './useObjectRef';
export * from './useObservableRef';
export * from './usePermission';
export * from './usePromiseState';
export * from './useS';
export * from './useStateDelay';
export * from './useErrorDetails';
export * from './useActivationDelay';
@@ -14,13 +14,13 @@ const styles = css`
height: 100%;
display: flex;
align-items: center;
padding: 0 8px;
padding: 0 14px;
cursor: pointer;
}
IconOrImage {
height: 24px;
width: 120px;
height: 32px;
width: 154px;
&[|small] {
display: none;
}
@@ -12,7 +12,7 @@ export const topAppBarStyles = css`
composes: theme-text-on-primary theme-typography--body2 from global;
display: flex;
align-items: center;
height: 32px;
height: 48px;
padding: 0 8px;
background-color: #2a7cb4;
z-index: 1;
-12
View File
@@ -1,12 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { type ClassValue, clsx } from '@cloudbeaver/core-utils';
export function s<T extends Record<string, string>>(styles: T, options?: Partial<Record<keyof T, boolean>>, ...inputs: ClassValue[]): string {
return clsx(...Object.entries(options ?? {}).map(([key, active]) => active && styles[key]), ...inputs);
}
-108
View File
@@ -1,108 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useContext, useMemo, useRef, useState } from 'react';
import { useService } from '@cloudbeaver/core-di';
import { BaseStyles, ComponentStyle, Style, ThemeService } from '@cloudbeaver/core-theming';
import { SContextReact } from './SContext';
import { useExecutor } from './useExecutor';
type Intersect<T> = (T extends any ? (x: T) => 0 : never) extends (x: infer R) => 0
? R extends Record<string, string>
? BaseStyles<R>
: never
: never;
type ExtractStyles<T extends ComponentStyle[]> = Intersect<
T extends (infer U)[]
? U extends BaseStyles
? U
: U extends (theme: string) => Promise<infer A>
? A extends BaseStyles
? A
: never
: never
: never
>;
/**
* Changes styles depending on theme
*
* @param componentStyles styles array
*/
export function useS<T extends ComponentStyle[]>(...componentStyles: [...T]): ExtractStyles<T> {
// todo do you understand that we store ALL STYLES in each component that uses this hook?
const context = useContext(SContextReact);
const stylesRef = useRef<ComponentStyle[]>([]);
const [patch, forceUpdate] = useState(0);
const loadedStyles = useRef<BaseStyles[]>([]);
const themeService = useService(ThemeService);
const [currentThemeId, setCurrentThemeId] = useState(() => themeService.currentThemeId);
const lastThemeRef = useRef<string>(currentThemeId);
const filteredStyles = themeService.mapStyles(componentStyles.flat(Infinity).filter(Boolean) as Style[], context);
const trackTheme = filteredStyles.some(style => typeof style === 'function');
useExecutor({
executor: themeService.onChange,
handlers: [
function updateThemeId(theme) {
if (currentThemeId !== themeService.currentThemeId && trackTheme) {
setCurrentThemeId(theme.id);
}
},
],
});
let changed = lastThemeRef.current !== currentThemeId || filteredStyles.length !== stylesRef.current.length;
for (let i = 0; !changed && i < filteredStyles.length; i++) {
changed = stylesRef.current[i] !== filteredStyles[i];
}
if (changed) {
stylesRef.current = filteredStyles;
lastThemeRef.current = currentThemeId;
const staticStyles: BaseStyles[] = [];
const themedStyles: Array<Promise<undefined | BaseStyles | BaseStyles[]>> = [];
for (const style of filteredStyles) {
const data = typeof style === 'object' ? style : style(currentThemeId);
if (data instanceof Promise) {
themedStyles.push(data);
} else {
staticStyles.push(data);
}
}
loadedStyles.current = staticStyles.flat(Infinity);
if (themedStyles.length > 0) {
Promise.all(themedStyles).then(styles => {
loadedStyles.current = [staticStyles, styles].flat(Infinity).filter(Boolean) as BaseStyles[];
forceUpdate(patch + 1);
});
}
}
const styles = useMemo(() => combineStyles(loadedStyles.current), [patch, loadedStyles.current]);
return styles as ExtractStyles<T>; // todo this method is called in each rerender
}
export function combineStyles(styles: BaseStyles[]): BaseStyles {
const combined: BaseStyles = {};
for (const style of styles) {
for (const [key, value] of Object.entries(style)) {
combined[key] = [combined[key] || '', value].join(' ').trim();
}
}
return combined;
}
+5 -6
View File
@@ -10,6 +10,7 @@ import { create } from 'reshadow';
import { useService } from '@cloudbeaver/core-di';
import { BaseStyles, ComponentStyle, Style, ThemeService } from '@cloudbeaver/core-theming';
import { flat } from '@cloudbeaver/core-utils';
import { useExecutor } from './useExecutor';
@@ -27,8 +28,7 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record<string,
const themeService = useService(ThemeService);
const [currentThemeId, setCurrentThemeId] = useState(() => themeService.currentThemeId);
const lastThemeRef = useRef<string>(currentThemeId);
//@ts-ignore
const filteredStyles = componentStyles.flat(Infinity).filter(Boolean) as Style[];
const filteredStyles = flat(componentStyles).filter(Boolean) as Style[];
const trackTheme = filteredStyles.some(style => typeof style === 'function');
useExecutor({
@@ -62,11 +62,11 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record<string,
staticStyles.push(data);
}
}
loadedStyles.current = staticStyles.flat(Infinity);
loadedStyles.current = flat(staticStyles);
if (themedStyles.length > 0) {
Promise.all(themedStyles).then(styles => {
loadedStyles.current = [staticStyles, styles].flat(Infinity).filter(Boolean) as BaseStyles[];
loadedStyles.current = flat([staticStyles, flat(styles)]).filter(Boolean) as BaseStyles[];
forceUpdate(patch + 1);
});
}
@@ -78,6 +78,5 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record<string,
}
export function joinStyles(...styles: ComponentStyle[]): ComponentStyle {
//@ts-ignore
return styles.flat(Infinity) as ComponentStyle;
return styles.flat(2);
}
@@ -55,13 +55,14 @@ module.exports = {
exclude: ['transform-async-to-generator', 'transform-regenerator'],
},
],
['@babel/preset-typescript', { isTSX: true, allExtensions: true, onlyRemoveTypeImports: true }],
[
'@babel/preset-react',
{
runtime: 'automatic',
'runtime': 'automatic',
// "importSource": "preact-jsx-runtime"
},
],
['@babel/preset-typescript', { isTSX: true, allExtensions: true, onlyRemoveTypeImports: true }],
],
plugins: [
'babel-plugin-transform-typescript-metadata',
@@ -72,8 +73,8 @@ module.exports = {
['@babel/plugin-proposal-class-properties'],
['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
[testingAttributes, {}],
[require('@reshadow/babel'), {}],
/*devMode &&*/ [
[require('@reshadow/babel'), { }],
/*devMode &&*/[
'babel-plugin-module-resolver',
{
alias: {
@@ -1,9 +1,8 @@
const { resolve } = require('path');
const { resolve, join } = require('path');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const ModuleDependencyWarning = require('webpack/lib/ModuleDependencyWarning');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const threadLoader = require('thread-loader');
const excludedFromVendor = require('./excludedFromVendor.js');
// const ESLintPlugin = require('eslint-webpack-plugin');
@@ -33,8 +32,6 @@ const nodeModules = [
resolve('../../node_modules/@cloudbeaver/core-cli/node_modules'), // core-cli
];
threadLoader.warmup({}, ['babel-loader', 'css-loader', 'sass-loader', 'postcss-loader', 'style-loader', 'json5-loader', MiniCssExtractPlugin.loader]);
module.exports = (env, argv) => {
process.env.NODE_ENV = argv.mode;
const devTool = 'source-map' in env && 'source-map';
@@ -52,7 +49,7 @@ module.exports = (env, argv) => {
return loaders;
}
function generateStyleLoaders(options = { hasModule: false, disable: false, pure: false }) {
function generateStyleLoaders(options = { hasModule: false, disable: false }) {
const moduleScope = options.hasModule ? 'local' : 'global';
let modules = {
mode: moduleScope,
@@ -63,11 +60,7 @@ module.exports = (env, argv) => {
modules = false;
}
const postCssPlugins = [require('postcss-preset-env')({ stage: 0 })];
if (!options.pure) {
postCssPlugins.push(require('@reshadow/postcss')({ scopeBehaviour: moduleScope }));
}
const postCssPlugins = [require('postcss-preset-env')({ stage: 0 }), require('@reshadow/postcss')({ scopeBehaviour: moduleScope })];
return [
...getBaseStyleLoaders(),
@@ -75,27 +68,26 @@ module.exports = (env, argv) => {
loader: 'css-loader',
options: {
modules: modules,
sourceMap: devMode,
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: devMode,
postcssOptions: {
plugins: postCssPlugins,
sourceMap: devMode,
sourceMap: true,
},
},
},
{
loader: 'sass-loader',
options: {
sourceMap: devMode,
sourceMap: true,
sassOptions: {
implementation: require('sass'),
includePaths: nodeModules,
quietDeps: true,
},
},
},
@@ -105,8 +97,7 @@ module.exports = (env, argv) => {
var babelLoader = {
loader: require.resolve('babel-loader'),
options: {
root: __dirname,
cacheDirectory: true,
configFile: join(__dirname, 'babel.config.js'),
},
};
@@ -208,16 +199,11 @@ module.exports = (env, argv) => {
},
{
test: /\.(css|s[ac]ss)$/,
exclude: /node_modules/,
oneOf: [
{
test: /\.module\.(css|s[ac]ss)$/,
use: generateStyleLoaders({ hasModule: true }),
},
{
test: /\.m\.(css|s[ac]ss)$/,
use: generateStyleLoaders({ hasModule: true, pure: true }),
},
{
include: /node_modules/,
use: [...getBaseStyleLoaders(), 'css-loader', 'sass-loader'],
@@ -43,8 +43,7 @@ module.exports = (env, argv) => {
sso,
},
optimization: {
minimize: false,
moduleIds: 'named',
minimize: true,
},
devServer: {
allowedHosts: 'all',
@@ -72,7 +71,10 @@ module.exports = (env, argv) => {
httpProxy.createProxyServer({ target:`http://127.0.0.1:${port}` }).listen(8080);
},
},
devtool: 'source-map',
devtool: 'eval-source-map',
// optimization: {
// moduleIds: 'named',
// },
plugins: [
new CopyWebpackPlugin({
patterns: getAssets(package, ''),
+2 -1
View File
@@ -7,7 +7,6 @@
"files": [
"/package.json",
"/tsconfig.json",
"/global.d.ts",
"/src",
"/configs",
"/bin",
@@ -72,6 +71,8 @@
},
"peerDependencies": {
"@types/jest": "~29.x.x",
"@types/react": "~18.x.x",
"@types/react-dom": "~18.x.x",
"babel-jest": "~29.x.x",
"jest": "~29.x.x",
"jest-environment-jsdom": "~29.x.x"
@@ -91,7 +91,7 @@ export const commonDialogBaseStyle = [
dialog-body,
footer {
flex-shrink: 0;
padding: 16px;
padding: 24px;
&[|no-padding] {
padding: 0px;
@@ -26,7 +26,7 @@ export const dialogStyles = css`
inner-box {
display: flex;
margin: auto;
padding: 16px;
padding: 24px;
flex-direction: column;
align-items: center;
@@ -29,6 +29,6 @@ export const styles = css`
CommonDialogFooter {
align-items: center;
justify-content: flex-end;
gap: 16px;
gap: 24px;
}
`;
@@ -7,10 +7,8 @@
*/
import type { ClassCollection } from './themeUtils';
export type BaseStyles<T extends Record<string, string> = Record<string, string>> = ClassCollection<T>;
export type ThemeSelector<T extends Record<string, string> = Record<string, string>> = (
theme: string,
) => Promise<undefined | BaseStyles<T> | BaseStyles<T>[]>;
export type Style<T extends Record<string, string> = Record<string, string>> = BaseStyles<T> | ThemeSelector<T>;
export type DynamicStyle<T extends Record<string, string> = Record<string, string>> = Style<T> | boolean | undefined;
export type ComponentStyle<T extends Record<string, string> = Record<string, string>> = DynamicStyle<T> | ComponentStyle<T>[];
export type BaseStyles = ClassCollection;
export type ThemeSelector = (theme: string) => Promise<undefined | BaseStyles | BaseStyles[]>;
export type Style = BaseStyles | ThemeSelector;
export type DynamicStyle = Style | boolean | undefined;
export type ComponentStyle = DynamicStyle | DynamicStyle[] | DynamicStyle[][];
@@ -13,7 +13,6 @@ import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { SettingsService } from '@cloudbeaver/core-settings';
import type { Style } from './ComponentStyle';
import './styles/main/base.pure.css';
import './styles/main/color.pure.scss';
import './styles/main/elevation.pure.scss';
@@ -38,11 +37,6 @@ interface ISettings {
currentThemeId: string;
}
export interface IStyleRegistry {
mode: 'replace' | 'append';
styles: Style[];
}
@injectable()
export class ThemeService extends Bootstrap {
get themes(): ITheme[] {
@@ -73,7 +67,6 @@ export class ThemeService extends Bootstrap {
readonly onChange: ISyncExecutor<ITheme>;
private readonly stylesRegistry: Map<Style, IStyleRegistry[]> = new Map();
private readonly themeMap: Map<string, ITheme> = new Map();
private readonly settings: ISettings;
@@ -102,37 +95,6 @@ export class ThemeService extends Bootstrap {
this.loadAllThemes();
}
addStyleRegistry<T extends Record<string, string>>(style: Style<T>, mode: 'replace' | 'append', styles: Style<T>[]): void {
if (!this.stylesRegistry.has(style)) {
this.stylesRegistry.set(style, []);
}
this.stylesRegistry.get(style)!.push({ mode, styles });
}
mapStyles<T extends Record<string, string>>(styles: Style<T>[], context?: Map<Style, IStyleRegistry[]>): Style<T>[] {
return styles
.map(style => {
const registries = this.stylesRegistry.get(style) ?? context?.get(style);
if (!registries) {
return [style];
}
return registries.reduce(
(acc, registry) => {
if (registry.mode === 'replace') {
acc = acc.filter(s => s !== style);
}
return [...acc, ...this.mapStyles(registry.styles, context)] as Style<T>[];
},
[style] as Style<T>[],
);
})
.flat();
}
async load(): Promise<void> {
await this.serverConfigResource.load();
this.setCurrentThemeId(this.defaultThemeId); // set default app theme
@@ -8,12 +8,10 @@
@mixin button-core-styles() {
$mdc-button-disabled-container-fill-color: #cccccc;
$mdc-button-density-scale: -3;
$mdc-density-default-scale: -3;
$mdc-button-density-scale: -2;
$query: mdc-feature-all();
@include mdc-button-core-styles;
@include mdc-button-theme-baseline;
@include mdc-button-density(-3);
.theme-button {
@extend .mdc-button;
@@ -86,4 +84,4 @@
@include mdc-states($color: on-secondary, $query: $query, $ripple-target: $mdc-button-ripple-target);
}
}
}
}
@@ -6,14 +6,11 @@
* you may not use this file except in compliance with the License.
*/
@import '@material/checkbox/mixins';
@import "@material/checkbox/mixins";
$mdc-checkbox-icon-size: 16px;
$mdc-checkbox-density-scale: -3;
$mdc-density-default-scale: -3;
@mixin checkbox-core-styles($query: mdc-feature-all()) {
@include mdc-checkbox-core-styles($query);
@include mdc-checkbox-density(-3);
.theme-checkbox {
@extend .mdc-checkbox;
@@ -51,14 +48,24 @@ $mdc-density-default-scale: -3;
$mdc-checkbox-mark-color: $mdc-theme-on-primary;
@include mdc-checkbox-ink-color($mdc-checkbox-mark-color, $query);
@include mdc-checkbox-disabled-ink-color(rgba($mdc-checkbox-mark-color, 0.5), $query);
@include mdc-checkbox-container-colors($mdc-checkbox-border-color, transparent, $mdc-theme-primary, $mdc-theme-primary);
@include mdc-checkbox-container-colors(
$mdc-checkbox-border-color,
transparent,
$mdc-theme-primary,
$mdc-theme-primary
);
}
.theme-checkbox_surface {
$mdc-checkbox-mark-color: $mdc-theme-primary;
@include mdc-checkbox-ink-color($mdc-checkbox-mark-color, $query);
@include mdc-checkbox-disabled-ink-color(rgba($mdc-checkbox-mark-color, 0.5), $query);
@include mdc-checkbox-container-colors($mdc-theme-surface, transparent, $mdc-theme-surface, $mdc-theme-surface);
@include mdc-checkbox-container-colors(
$mdc-theme-surface,
transparent,
$mdc-theme-surface,
$mdc-theme-surface
);
}
.theme-checkbox_small {
@@ -6,15 +6,10 @@
* you may not use this file except in compliance with the License.
*/
@import '@material/radio/mixins';
$mdc-radio-density-scale: -3;
$mdc-density-default-scale: -3;
$mdc-radio-touch-area: 32px !default;
$mdc-radio-ui-size: 16px !default;
@import "@material/radio/mixins";
@mixin radio-core-styles($query: mdc-feature-all()) {
@include mdc-radio-core-styles;
@include mdc-radio-density(-3);
.theme-radio {
@extend .mdc-radio;
@@ -69,4 +64,4 @@ $mdc-radio-ui-size: 16px !default;
.theme-radio_no-ripple {
@include mdc-radio-ripple-size(0);
}
}
}
@@ -7,12 +7,10 @@
*/
@import '@material/switch/mixins';
$mdc-switch-density-scale: -3;
$mdc-density-default-scale: -3;
@mixin switch-core-styles($query: mdc-feature-all()) {
@include mdc-switch-core-styles($query);
.theme-switch {
@extend .mdc-switch;
}
@@ -43,8 +41,7 @@ $mdc-density-default-scale: -3;
@include switch-core-styles-dense($query);
}
.theme-switch_dense .theme-switch_primary,
.theme-switch_primary {
.theme-switch_dense .theme-switch_primary, .theme-switch_primary {
$mdc-switch-baseline-theme-color: primary;
@include mdc-switch-toggled-on-track-color($mdc-switch-baseline-theme-color, $query);
@@ -74,7 +71,8 @@ $mdc-density-default-scale: -3;
$mdc-switch-thumb-offset: 2px !global;
$mdc-switch-thumb-active-margin: $mdc-switch-track-width - $mdc-switch-thumb-diameter + $mdc-switch-thumb-offset * 2 !global;
$mdc-switch-thumb-active-margin:
$mdc-switch-track-width - $mdc-switch-thumb-diameter + $mdc-switch-thumb-offset * 2 !global;
@include mdc-switch-core-styles($query);
}
}
@@ -20,5 +20,5 @@
@include form-controls;
@include link;
@include placeholder;
@include delimiter($mdc-theme-background);
@include delimiter($mdc-theme-secondary);
@include branding-overflow;
@@ -20,5 +20,5 @@
@include form-controls;
@include link;
@include placeholder;
@include delimiter($mdc-theme-background);
@include delimiter($mdc-theme-secondary);
@include branding-overflow;
@@ -6,9 +6,9 @@
* you may not use this file except in compliance with the License.
*/
export type ClassCollection<T extends Record<string, string> = Record<string, string>> = {
[Key in keyof T]: string;
};
export interface ClassCollection {
[key: string]: any;
}
export class Composes {
composes: ClassCollection;
@@ -8,8 +8,20 @@
import { observer } from 'mobx-react-lite';
import { ButtonHTMLAttributes, forwardRef, useRef, useState } from 'react';
import type { MenuInitialState } from 'reakit/Menu';
import styled from 'reshadow';
import { getComputed, IMenuState, IMouseContextMenu, Menu, useAutoLoad, useObjectRef, useTranslate } from '@cloudbeaver/core-blocks';
import {
getComputed,
IMenuState,
IMouseContextMenu,
Menu,
menuPanelStyles,
useAutoLoad,
useObjectRef,
useStyles,
useTranslate,
} from '@cloudbeaver/core-blocks';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { IMenuData, MenuActionItem } from '@cloudbeaver/core-view';
import { MenuItemRenderer } from './MenuItemRenderer';
@@ -21,9 +33,10 @@ interface IMenuProps extends React.PropsWithChildren {
type ContextMenuRenderingChildren = (props: IMenuProps) => React.ReactNode | React.ReactElement;
interface IContextMenuProps extends Omit<ButtonHTMLAttributes<any>, 'children'> {
interface IContextMenuProps extends Omit<ButtonHTMLAttributes<any>, 'style' | 'children'> {
mouseContextMenu?: IMouseContextMenu;
menu: IMenuData;
style?: ComponentStyle;
disclosure?: boolean;
placement?: MenuInitialState['placement'];
modal?: boolean;
@@ -35,7 +48,7 @@ interface IContextMenuProps extends Omit<ButtonHTMLAttributes<any>, 'children'>
export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(
forwardRef(function ContextMenu(
{ mouseContextMenu, menu: menuData, disclosure, children, placement, visible, onVisibleSwitch, modal, rtl, ...props },
{ mouseContextMenu, menu: menuData, disclosure, children, style, placement, visible, onVisibleSwitch, modal, rtl, ...props },
ref,
) {
const translate = useTranslate();
@@ -48,6 +61,7 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(
const lazy = getComputed(() => !menuData.available || hidden);
const menu = useRef<IMenuState>();
const styles = useStyles(menuPanelStyles, style);
useAutoLoad(menuData.loaders, !lazy, menuVisible);
@@ -78,7 +92,7 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(
const renderingChildren: React.ReactNode = typeof children === 'function' ? children({ loading, disabled }) : children;
return (
return styled(styles)(
<Menu
ref={ref}
label={translate(menuData.menu.label)}
@@ -87,7 +101,16 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(
menuData.items.map(
item =>
menu.current && (
<MenuItemRenderer key={item.id} item={item} menuData={menuData} rtl={rtl} modal={modal} onItemClose={handlers.handleItemClose} />
<MenuItemRenderer
key={item.id}
item={item}
menuData={menuData}
rtl={rtl}
menu={menu.current}
modal={modal}
style={style}
onItemClose={handlers.handleItemClose}
/>
),
)
}
@@ -99,12 +122,13 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(
placement={placement}
disabled={disabled}
disclosure={disclosure}
style={style}
getHasBindings={handlers.hasBindings}
onVisibleSwitch={handlers.handleVisibleSwitch}
{...props}
>
{renderingChildren}
</Menu>
</Menu>,
);
}),
);
@@ -5,10 +5,15 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { MenuStateReturn } from 'reakit/ts';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import type { IMenuData, IMenuItem } from '@cloudbeaver/core-view';
export interface IContextMenuItemProps {
item: IMenuItem;
menu: MenuStateReturn;
menuData: IMenuData;
style?: ComponentStyle;
onClick: () => void;
}
@@ -7,8 +7,10 @@
*/
import { observer } from 'mobx-react-lite';
import React from 'react';
import { MenuItem, MenuItemCheckbox, MenuItemRadio } from 'reakit/Menu';
import styled, { use } from 'reshadow';
import { Checkbox, MenuItem, MenuItemCheckbox, MenuItemElement, MenuItemRadio, Radio } from '@cloudbeaver/core-blocks';
import { Checkbox, MenuItemElement, menuPanelStyles, Radio, useStyles } from '@cloudbeaver/core-blocks';
import type { IMenuActionItem } from '@cloudbeaver/core-view';
import type { IContextMenuItemProps } from './IContextMenuItemProps';
@@ -17,7 +19,8 @@ interface IMenuActionElementProps extends IContextMenuItemProps {
item: IMenuActionItem;
}
export const MenuActionElement = observer<IMenuActionElementProps>(function MenuActionElement({ item, onClick }) {
export const MenuActionElement = observer<IMenuActionElementProps>(function MenuActionElement({ item, menu, style, onClick }) {
const styles = useStyles(menuPanelStyles, style);
const actionInfo = item.action.actionInfo;
const loading = item.action.isLoading();
@@ -28,9 +31,10 @@ export const MenuActionElement = observer<IMenuActionElementProps>(function Menu
if (actionInfo.type === 'select') {
const checked = item.action.isChecked();
return (
return styled(styles)(
<MenuItemRadio
hidden={item.hidden}
{...menu}
{...use({ hidden: item.hidden })}
id={item.id}
aria-label={actionInfo.label}
disabled={item.disabled}
@@ -44,16 +48,18 @@ export const MenuActionElement = observer<IMenuActionElementProps>(function Menu
icon={<Radio checked={checked} mod={['primary', 'menu']} ripple={false} />}
tooltip={actionInfo.tooltip}
loading={loading}
style={style}
/>
</MenuItemRadio>
</MenuItemRadio>,
);
}
if (actionInfo.type === 'checkbox') {
const checked = item.action.isChecked();
return (
return styled(styles)(
<MenuItemCheckbox
hidden={item.hidden}
{...menu}
{...use({ hidden: item.hidden })}
id={item.id}
aria-label={actionInfo.label}
disabled={item.disabled}
@@ -64,23 +70,25 @@ export const MenuActionElement = observer<IMenuActionElementProps>(function Menu
>
<MenuItemElement
label={actionInfo.label}
icon={<Checkbox checked={checked} mod={['primary', 'small']} ripple={false} />}
icon={<Checkbox checked={checked} mod={['primary', 'small']} style={style} ripple={false} />}
tooltip={actionInfo.tooltip}
loading={loading}
style={style}
/>
</MenuItemCheckbox>
</MenuItemCheckbox>,
);
}
return (
<MenuItem hidden={item.hidden} id={item.id} aria-label={actionInfo.label} disabled={item.disabled} onClick={handleClick}>
return styled(styles)(
<MenuItem {...menu} {...use({ hidden: item.hidden })} id={item.id} aria-label={actionInfo.label} disabled={item.disabled} onClick={handleClick}>
<MenuItemElement
label={actionInfo.label}
icon={actionInfo.icon}
binding={item.action.binding?.binding.label}
tooltip={actionInfo.tooltip}
loading={loading}
style={style}
/>
</MenuItem>
</MenuItem>,
);
});
@@ -0,0 +1,70 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const MENU_BAR_DEFAULT_STYLES = css`
menu-bar {
composes: theme-background-secondary theme-text-on-secondary theme-border-color-background theme-typography--body2 from global;
height: 48px;
display: flex;
}
menu-bar-item {
composes: theme-ripple from global;
padding: 0 16px;
cursor: pointer;
background: transparent;
outline: none;
color: inherit;
& menu-bar-item-box {
display: flex;
align-items: center;
}
&[use|hidden] {
display: none;
}
& menu-bar-item-icon {
display: flex;
width: 24px;
}
& Loader,
& IconOrImage {
width: 24px;
}
& menu-bar-item-label {
display: block;
text-transform: uppercase;
font-weight: 700;
}
& menu-bar-item-icon + menu-bar-item-label {
padding-left: 8px;
}
& menu-bar-item-mark {
display: none;
}
}
MenuSeparator {
composes: theme-border-color-background from global;
height: 100%;
margin: 0;
border: 1px solid !important;
&:first-child,
&:last-child {
display: none;
}
}
`;
@@ -0,0 +1,31 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const MENU_BAR_SMALL_STYLES = css`
menu-bar-item {
padding: 0 12px;
& menu-bar-item-icon {
display: flex;
width: 16px;
}
& menu-bar-item-label {
font-size: 12px;
}
& Loader,
& IconOrImage {
width: 16px;
}
}
menu-bar {
height: 32px;
}
`;
@@ -1,19 +0,0 @@
.menuBar {
height: 32px;
display: flex;
& > .menuSeparator {
height: 100%;
margin: 0;
border: 1px solid !important;
&:first-child,
&:last-child {
display: none;
}
}
}
.floating.menuBar {
height: 28px;
}
@@ -7,9 +7,11 @@
*/
import { observer } from 'mobx-react-lite';
import { forwardRef, useCallback } from 'react';
import type { MenuInitialState } from 'reakit';
import { MenuInitialState, MenuSeparator } from 'reakit';
import styled, { use } from 'reshadow';
import { getComputed, MenuSeparator, MenuSeparatorStyles, s, SContext, StyleRegistry, useAutoLoad, useS } from '@cloudbeaver/core-blocks';
import { getComputed, Loader, useAutoLoad, useStyles } from '@cloudbeaver/core-blocks';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import {
DATA_CONTEXT_MENU_NESTED,
DATA_CONTEXT_SUBMENU_ITEM,
@@ -24,32 +26,22 @@ import {
} from '@cloudbeaver/core-view';
import { ContextMenu } from '../ContextMenu';
import style from './MenuBar.m.css';
import { MenuBarItem } from './MenuBarItem';
interface INestedMenuSettings extends MenuInitialState {
onVisibleSwitch?: (visible: boolean) => void;
}
interface IMenuBarProps extends React.HTMLAttributes<HTMLDivElement> {
interface IMenuBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style'> {
menu: IMenuData;
nestedMenuSettings?: INestedMenuSettings;
style?: ComponentStyle;
rtl?: boolean;
}
const registry: StyleRegistry = [
[
MenuSeparatorStyles,
{
mode: 'append',
styles: [style],
},
],
];
export const MenuBar = observer<IMenuBarProps, HTMLDivElement>(
forwardRef(function MenuBar({ menu, nestedMenuSettings, rtl, className, ...props }, ref) {
const styles = useS(style);
forwardRef(function MenuBar({ menu, nestedMenuSettings, style, rtl, ...props }, ref) {
const styles = useStyles(style);
const items = menu.items;
useAutoLoad(menu.loaders);
@@ -57,14 +49,12 @@ export const MenuBar = observer<IMenuBarProps, HTMLDivElement>(
return null;
}
return (
<SContext registry={registry}>
<div ref={ref} className={s(styles, { menuBar: true }, className)} {...props}>
{items.map(item => (
<MenuBarElement key={item.id} item={item} menuData={menu} nestedMenuSettings={nestedMenuSettings} rtl={rtl} />
))}
</div>
</SContext>
return styled(styles)(
<menu-bar ref={ref} as="div" {...props}>
{items.map(item => (
<MenuBarElement key={item.id} item={item} menuData={menu} nestedMenuSettings={nestedMenuSettings} rtl={rtl} style={style} />
))}
</menu-bar>,
);
}),
);
@@ -75,9 +65,12 @@ interface IMenuBarElementProps {
nestedMenuSettings?: INestedMenuSettings;
className?: string;
rtl?: boolean;
style?: ComponentStyle;
}
const MenuBarElement = observer<IMenuBarElementProps>(function MenuBarElement({ item, menuData, nestedMenuSettings, className, rtl }) {
const MenuBarElement = observer<IMenuBarElementProps>(function MenuBarElement({ item, menuData, nestedMenuSettings, className, rtl, style }) {
const styles = useStyles(style);
const onClick = useCallback(() => {
item.events?.onSelect?.();
}, [item]);
@@ -87,30 +80,33 @@ const MenuBarElement = observer<IMenuBarElementProps>(function MenuBarElement({
}
if (item instanceof MenuSubMenuItem) {
return <SubMenuItem item={item} menuData={menuData} className={className} rtl={rtl} nestedMenuSettings={nestedMenuSettings} />;
return styled(styles)(
<SubMenuItem item={item} menuData={menuData} style={style} className={className} rtl={rtl} nestedMenuSettings={nestedMenuSettings} />,
);
}
if (item instanceof MenuSeparatorItem) {
return <MenuSeparator className={className} />;
return styled(styles)(<MenuSeparator className={className} />);
}
if (item instanceof MenuActionItem) {
return <MenuBarAction item={item} className={className} onClick={onClick} />;
return styled(styles)(<MenuBarAction item={item} style={style} className={className} onClick={onClick} />);
}
if (item instanceof MenuBaseItem) {
return (
return styled(styles)(
<MenuBarItem
id={item.id}
hidden={item.hidden}
aria-label={item.label}
label={item.label}
icon={item.icon}
title={item.tooltip}
disabled={item.disabled}
style={style}
className={className}
onClick={onClick}
/>
{...use({ hidden: item.hidden })}
/>,
);
}
@@ -119,11 +115,14 @@ const MenuBarElement = observer<IMenuBarElementProps>(function MenuBarElement({
interface IMenuBarActionProps {
item: IMenuActionItem;
style?: ComponentStyle;
className?: string;
onClick: () => void;
}
const MenuBarAction = observer<IMenuBarActionProps>(function MenuBarAction({ item, className, onClick }) {
const MenuBarAction = observer<IMenuBarActionProps>(function MenuBarAction({ item, style, className, onClick }) {
const styles = useStyles(style);
const actionInfo = item.action.actionInfo;
const loading = item.action.isLoading();
@@ -135,10 +134,9 @@ const MenuBarAction = observer<IMenuBarActionProps>(function MenuBarAction({ ite
item.action.activate();
}
return (
return styled(styles)(
<MenuBarItem
id={item.id}
hidden={item.hidden}
aria-label={actionInfo.label}
label={actionInfo.label}
displayLabel={displayLabel}
@@ -146,9 +144,11 @@ const MenuBarAction = observer<IMenuBarActionProps>(function MenuBarAction({ ite
title={actionInfo.tooltip}
disabled={item.disabled}
loading={loading}
style={style}
className={className}
onClick={handleClick}
/>
{...use({ hidden: item.hidden })}
/>,
);
});
@@ -158,9 +158,11 @@ interface ISubMenuItemProps {
nestedMenuSettings?: INestedMenuSettings;
className?: string;
rtl?: boolean;
style?: ComponentStyle;
}
const SubMenuItem = observer<ISubMenuItemProps>(function SubmenuItem({ item, menuData, nestedMenuSettings, className, rtl }) {
const SubMenuItem = observer<ISubMenuItemProps>(function SubmenuItem({ item, menuData, nestedMenuSettings, className, rtl, style }) {
const styles = useStyles(style);
const subMenuData = useMenu({ menu: item.menu, context: menuData.context });
subMenuData.context.set(DATA_CONTEXT_MENU_NESTED, true);
@@ -187,22 +189,31 @@ const SubMenuItem = observer<ISubMenuItemProps>(function SubmenuItem({ item, men
const tooltip = info?.tooltip ?? item.tooltip ?? item.menu.tooltip;
const panelAvailable = subMenuData.itemCreators.length > 0;
return (
<ContextMenu menu={subMenuData} className={className} rtl={rtl} disclosure {...nestedMenuSettings}>
return styled(styles)(
<ContextMenu menu={subMenuData} style={style} className={className} rtl={rtl} disclosure {...nestedMenuSettings}>
{({ loading, disabled }) => (
<MenuBarItem
id={item.id}
hidden={item.hidden}
aria-label={item.menu.label}
label={label}
displayLabel={displayLabel}
icon={IconComponent ? <IconComponent item={item} {...extraProps} /> : icon}
icon={
IconComponent
? styled(styles)(
<Loader suspense small fullSize>
<IconComponent item={item} style={style} {...extraProps} />
</Loader>,
)
: icon
}
title={tooltip}
loading={loading}
disabled={disabled}
style={style}
displaySubmenuMark={panelAvailable}
{...use({ hidden: item.hidden })}
/>
)}
</ContextMenu>
</ContextMenu>,
);
});
@@ -1,78 +0,0 @@
.menuBarItem {
composes: theme-ripple from global;
padding: 0 8px;
cursor: pointer;
background: transparent;
outline: none;
color: inherit;
&.hidden {
display: none;
}
& .menuBarItemBox {
display: flex;
align-items: center;
}
& .menuBarItemIcon {
display: flex;
width: 24px;
height: 24px;
padding: 4px;
box-sizing: border-box;
align-items: center;
justify-content: center;
&:empty {
display: none;
}
& .iconOrImage {
display: block;
object-fit: contain;
}
}
& .loader,
& .iconOrImage {
width: 16px;
}
& .menuBarItemLabel {
display: block;
text-transform: uppercase;
font-weight: 500;
font-size: 12px;
padding: 0 4px;
}
& .menuBarItemIcon + .menuBarItemLabel {
padding-left: 4px;
}
& .menuBarItemMark {
display: none;
& .icon {
width: 12px;
}
}
}
.floating {
& .menuBarItem {
padding: 4px;
margin: 2px;
width: 24px;
height: 24px;
overflow: hidden;
flex-shrink: 0;
border-radius: var(--theme-form-element-radius);
}
& .menuBarItemIcon {
width: 16px;
height: 16px;
padding: 0px;
}
}
@@ -7,62 +7,49 @@
*/
import { observer } from 'mobx-react-lite';
import { ButtonHTMLAttributes, forwardRef } from 'react';
import styled from 'reshadow';
import { Icon, IconOrImage, Loader, s, useS, useStateDelay, useTranslate } from '@cloudbeaver/core-blocks';
import style from './MenuBarItem.m.css';
import { Icon, IconOrImage, Loader, useStateDelay, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
interface Props extends Omit<React.DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, 'style'> {
label?: string;
/** @deprecated must be refactored (#1)*/
displayLabel?: boolean;
loading?: boolean;
hidden?: boolean;
icon?: string | React.ReactNode;
displaySubmenuMark?: boolean;
viewBox?: string;
style?: ComponentStyle;
}
export const MenuBarItem = observer<Props, HTMLButtonElement>(
forwardRef(function MenuBarItem(
{ label, displayLabel = true, loading = false, hidden, icon, displaySubmenuMark, viewBox = '0 0 24 24', className, ...rest },
{ label, displayLabel = true, loading = false, icon, displaySubmenuMark, viewBox = '0 0 24 24', style = [], ...rest },
ref,
) {
const styles = useS(style);
const translate = useTranslate();
loading = useStateDelay(loading, 100);
const title = translate(rest.title);
return (
<button ref={ref} className={s(styles, { menuBarItem: true, hidden }, className)} {...rest} title={title} aria-label={title}>
<div className={s(styles, { menuBarItemBox: true }, className)}>
return styled(useStyles(style))(
<menu-bar-item ref={ref} as="button" {...rest} title={title} aria-label={title}>
<menu-bar-item-box>
{loading ? (
<div className={s(styles, { menuBarItemIcon: true }, className)}>
<Loader className={s(styles, { loader: true }, className)} small fullSize />
</div>
<menu-bar-item-icon>
<Loader small fullSize />
</menu-bar-item-icon>
) : (
icon && (
<div className={s(styles, { menuBarItemIcon: true }, className)}>
<Loader className={s(styles, { loader: true }, className)} suspense small fullSize>
{typeof icon === 'string' ? (
<IconOrImage className={s(styles, { iconOrImage: true }, className)} icon={icon} viewBox={viewBox} />
) : (
icon
)}
</Loader>
</div>
)
)}
{label && displayLabel && (
<div className={s(styles, { menuBarItemLabel: true }, className)}>{translate(label)}</div>
icon && <menu-bar-item-icon>{typeof icon === 'string' ? <IconOrImage icon={icon} viewBox={viewBox} /> : icon}</menu-bar-item-icon>
)}
{label && displayLabel ? <menu-bar-item-label>{translate(label)}</menu-bar-item-label> : <padding />}
{displaySubmenuMark && (
<div className={s(styles, { menuBarItemMark: true }, className)}>
<Icon className={s(styles, { icon: true }, className)} name="angle" viewBox="0 0 15 8" />
</div>
<menu-bar-item-mark>
<Icon name="angle" viewBox="0 0 15 8" />
</menu-bar-item-mark>
)}
</div>
</button>
</menu-bar-item-box>
</menu-bar-item>,
);
}),
);
@@ -7,8 +7,11 @@
*/
import { observer } from 'mobx-react-lite';
import React, { useCallback } from 'react';
import { MenuItem, MenuItemCheckbox, MenuSeparator, MenuStateReturn } from 'reakit/Menu';
import styled, { use } from 'reshadow';
import { Checkbox, MenuItem, MenuItemCheckbox, MenuItemElement, MenuSeparator } from '@cloudbeaver/core-blocks';
import { Checkbox, joinStyles, Loader, MenuItemElement, menuPanelStyles, useStyles } from '@cloudbeaver/core-blocks';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import {
IMenuData,
IMenuItem,
@@ -23,15 +26,18 @@ import {
import { MenuActionElement } from './MenuActionElement';
import { SubMenuElement } from './SubMenuElement';
export interface IMenuItemRendererProps extends React.ButtonHTMLAttributes<any> {
export interface IMenuItemRendererProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
item: IMenuItem;
menuData: IMenuData;
menu: MenuStateReturn; // from reakit useMenuState
modal?: boolean;
rtl?: boolean;
onItemClose?: () => void;
style?: ComponentStyle;
}
export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuItemRenderer({ item, modal, rtl, menuData, onItemClose }) {
export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuItemRenderer({ item, modal, rtl, menuData, menu, onItemClose, style }) {
const styles = useStyles(menuPanelStyles, style);
const onClick = useCallback(
(keepMenuOpen = true) => {
item.events?.onSelect?.();
@@ -46,39 +52,42 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
if (item instanceof MenuCustomItem) {
const CustomMenuItem = item.getComponent();
return <CustomMenuItem item={item} menuData={menuData} onClick={onClick} />;
return styled(styles)(<CustomMenuItem item={item} menu={menu} menuData={menuData} style={style} onClick={onClick} />);
}
if (item instanceof MenuSubMenuItem) {
return (
return styled(styles)(
<MenuItem
{...menu}
{...{ as: SubMenuElement }}
{...use({ hidden: item.hidden })}
id={item.id}
aria-label={item.menu.label}
hidden={item.hidden}
itemRenderer={MenuItemRenderer}
menuRtl={rtl}
menuData={menuData}
menuModal={modal}
subMenu={item}
style={style}
onItemClose={onItemClose}
onClick={() => onClick()}
/>
/>,
);
}
if (item instanceof MenuSeparatorItem) {
return <MenuSeparator />;
return styled(styles)(<MenuSeparator {...menu} />);
}
if (item instanceof MenuActionItem) {
return <MenuActionElement item={item} menuData={menuData} onClick={onClick} />;
return <MenuActionElement item={item} menu={menu} menuData={menuData} style={style} onClick={onClick} />;
}
if (item instanceof MenuCheckboxItem) {
return (
return styled(styles)(
<MenuItemCheckbox
hidden={item.hidden}
{...menu}
{...use({ hidden: item.hidden })}
id={item.id}
aria-label={item.label}
disabled={item.disabled}
@@ -89,10 +98,11 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
>
<MenuItemElement
label={item.label}
icon={<Checkbox checked={item.checked} mod={['primary', 'small']} ripple={false} />}
icon={<Checkbox checked={item.checked} mod={['primary', 'small']} style={style} ripple={false} />}
tooltip={item.tooltip}
style={style}
/>
</MenuItemCheckbox>
</MenuItemCheckbox>,
);
}
@@ -100,10 +110,31 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
const IconComponent = item.iconComponent?.();
const extraProps = item.getExtraProps?.();
return (
<MenuItem id={item.id} aria-label={item.label} hidden={item.hidden} disabled={item.disabled} onClick={onClick} {...extraProps}>
<MenuItemElement label={item.label} icon={IconComponent ? <IconComponent item={item} {...extraProps} /> : item.icon} tooltip={item.tooltip} />
</MenuItem>
return styled(styles)(
<MenuItem
{...menu}
{...use({ hidden: item.hidden })}
id={item.id}
aria-label={item.label}
disabled={item.disabled}
onClick={onClick}
{...extraProps}
>
<MenuItemElement
label={item.label}
icon={
IconComponent
? styled(styles)(
<Loader suspense inline fullSize>
<IconComponent item={item} style={joinStyles(menuPanelStyles, style)} {...extraProps} />
</Loader>,
)
: item.icon
}
tooltip={item.tooltip}
style={style}
/>
</MenuItem>,
);
}
@@ -7,8 +7,21 @@
*/
import { observer } from 'mobx-react-lite';
import { forwardRef, useRef, useState } from 'react';
import styled from 'reshadow';
import { getComputed, IMenuState, Menu, MenuItemElement, useAutoLoad, useObjectRef } from '@cloudbeaver/core-blocks';
import {
getComputed,
IMenuState,
joinStyles,
Loader,
Menu,
MenuItemElement,
menuPanelStyles,
useAutoLoad,
useObjectRef,
useStyles,
} from '@cloudbeaver/core-blocks';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { DATA_CONTEXT_MENU_NESTED, DATA_CONTEXT_SUBMENU_ITEM, IMenuData, IMenuSubMenuItem, MenuActionItem, useMenu } from '@cloudbeaver/core-view';
import type { IMenuItemRendererProps } from './MenuItemRenderer';
@@ -20,11 +33,13 @@ interface ISubMenuElementProps extends Omit<React.ButtonHTMLAttributes<any>, 'st
menuModal?: boolean;
menuRtl?: boolean;
onItemClose?: () => void;
style?: ComponentStyle;
}
export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
forwardRef(function SubMenuElement({ menuData, subMenu, itemRenderer, menuModal: modal, menuRtl: rtl, onItemClose, ...rest }, ref) {
forwardRef(function SubMenuElement({ menuData, subMenu, itemRenderer, style, menuModal: modal, menuRtl: rtl, onItemClose, ...rest }, ref) {
const menu = useRef<IMenuState>();
const styles = useStyles(menuPanelStyles, style);
const subMenuData = useMenu({ menu: subMenu.menu, context: menuData.context });
const [visible, setVisible] = useState(false);
subMenuData.context.set(DATA_CONTEXT_MENU_NESTED, true);
@@ -74,7 +89,7 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
const MenuItemRenderer = itemRenderer;
const panelAvailable = subMenuData.available || !loaded;
return (
return styled(styles)(
<Menu
ref={ref}
menuRef={menu}
@@ -83,12 +98,22 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
subMenuData.items.map(
item =>
menu.current && (
<MenuItemRenderer key={item.id} item={item} menuData={subMenuData} rtl={rtl} modal={modal} onItemClose={handlers.handleItemClose} />
<MenuItemRenderer
key={item.id}
item={item}
menuData={subMenuData}
menu={menu.current}
rtl={rtl}
modal={modal}
style={style}
onItemClose={handlers.handleItemClose}
/>
),
)
}
rtl={rtl}
modal={modal}
style={style}
placement={rtl ? 'left-start' : 'right-start'}
panelAvailable={panelAvailable}
disabled={disabled}
@@ -100,13 +125,22 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
<MenuItemElement
label={label}
displayLabel={displayLabel}
icon={IconComponent ? <IconComponent item={subMenu} {...extraProps} /> : icon}
icon={
IconComponent
? styled(styles)(
<Loader suspense small fullSize>
<IconComponent item={subMenu} style={joinStyles(menuPanelStyles, style)} {...extraProps} />
</Loader>,
)
: icon
}
tooltip={tooltip}
panelAvailable={panelAvailable}
loading={loading}
style={style}
menu
/>
</Menu>
</Menu>,
);
}),
);
@@ -28,7 +28,7 @@ const styles = css`
CommonDialogFooter {
align-items: center;
justify-content: flex-end;
gap: 16px;
gap: 24px;
}
`;
@@ -25,6 +25,10 @@ const tabsStyles = css`
align-items: center;
overflow: auto;
}
Tab {
height: 36px !important;
font-weight: 500 !important;
}
TabPanel {
display: flex;
flex-direction: column;
@@ -19,7 +19,7 @@ export const BASE_TAB_STYLES = css`
padding: 0;
white-space: nowrap;
height: 32px;
height: 48px;
border-top: solid 2px transparent;
&:global([aria-selected='true']) {
@@ -63,29 +63,25 @@ export const BASE_TAB_STYLES = css`
align-items: center;
& tab-icon {
display: flex;
height: 24px;
padding-left: 12px;
justify-content: center;
align-items: center;
& StaticImage,
& IconOrImage,
& img {
width: 16px;
height: 16px;
width: 24px;
}
& placeholder {
width: 16px;
height: 16px;
width: 24px;
height: 24px;
}
}
& tab-title {
overflow: hidden;
text-overflow: ellipsis;
padding: 0 8px;
padding: 0 12px;
}
& tab-title placeholder {
@@ -12,7 +12,7 @@ export const UNDERLINE_TAB_STYLES = css`
display: flex;
}
Tab {
composes: theme-typography--caption from global;
composes: theme-typography--body2 from global;
background: none;
color: inherit;
border: none;
@@ -20,9 +20,9 @@ export const UNDERLINE_TAB_STYLES = css`
outline: none;
opacity: 1;
height: 30px !important;
padding: 0 14px !important;
border-top: none !important;
font-weight: normal !important;
text-transform: uppercase;
&:global([aria-selected='false']) {
opacity: 0.8;
@@ -12,7 +12,7 @@ export const verticalTabStyles = css`
overflow: auto;
border: none !important;
flex-shrink: 0;
height: 32px !important;
height: 36px !important;
}
vertical-tabs {
display: flex;
+2 -2
View File
@@ -4,8 +4,8 @@ export * from './Clipboard/ClipboardService';
export * from './ContextMenu/ContextMenu';
export * from './ContextMenu/IContextMenuItemProps';
export * from './ContextMenu/MenuBar/MenuBar';
export { default as MenuBarStyles } from './ContextMenu/MenuBar/MenuBar.m.css';
export { default as MenuBarItemStyles } from './ContextMenu/MenuBar/MenuBarItem.m.css';
export * from './ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES';
export * from './ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES';
export * from './DragAndDrop/DND_ELEMENT_TYPE';
export * from './DragAndDrop/DND_NATIVE_TYPE';
+1 -1
View File
@@ -6,4 +6,4 @@
* you may not use this file except in compliance with the License.
*/
export { default as clsx, type ClassValue } from 'clsx';
export { default as clsx } from 'clsx';
@@ -5,6 +5,8 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import type { IMenuItem } from './IMenuItem';
interface IMenuBaseItemCommonProperties<TExtraProps = unknown> {
@@ -21,6 +23,7 @@ export type MenuBaseItemIconComponent<TExtraProps = unknown> = React.FC<IIconCom
export interface IIconComponentProps<TExtraProps = unknown> {
item: IMenuBaseItem<TExtraProps>;
style?: ComponentStyle;
className?: string;
}
@@ -5,6 +5,8 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import type { IMenu } from '../IMenu';
import type { IMenuItem, IMenuItemEvents } from './IMenuItem';
@@ -29,6 +31,7 @@ export type MenuSubMenuItemIconComponent<TExtraProps = unknown> = React.FC<ISubM
export interface ISubMenuIconComponentProps<TExtraProps = unknown> {
item: IMenuSubMenuItem<TExtraProps>;
style?: ComponentStyle;
className?: string;
}
@@ -56,7 +56,6 @@
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/return-await": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-for-in-array": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": "warn",
@@ -167,12 +166,11 @@
"react/display-name": "off",
"react/jsx-no-literals": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-curly-newline": "off",
"react/jsx-wrap-multilines": "off",
"react/jsx-closing-bracket-location": "error",
"react/jsx-closing-tag-location": "error",
"react/jsx-boolean-value": "error",
"react/self-closing-comp": "error",
"react/jsx-curly-newline": "off",
"react/jsx-curly-brace-presence": ["error", "never"],
"react/jsx-fragments": ["off", "syntax"],
"react/jsx-equals-spacing": ["error", "never"],
@@ -199,6 +197,18 @@
"beforeClosing": "never"
}
],
"react/jsx-wrap-multilines": [
"error",
{
"declaration": "parens-new-line",
"assignment": "parens-new-line",
"return": "parens-new-line",
"arrow": "parens-new-line",
"condition": "parens-new-line",
"logical": "parens-new-line",
"prop": "parens-new-line"
}
],
"react/jsx-sort-props": [
"error",
{
@@ -40,7 +40,7 @@ const administrationStyles = css`
}
TabList {
width: 240px;
padding-top: 8px;
padding-top: 16px;
border-right: 2px solid;
}
content-container {
@@ -6,18 +6,17 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { AppAuthService } from '@cloudbeaver/core-authentication';
import { s, useS } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { MenuBar } from '@cloudbeaver/core-ui';
import { useMenu } from '@cloudbeaver/core-view';
import { TopMenuWrapperStyles } from '@cloudbeaver/plugin-top-app-bar';
import { MENU_BAR_DISABLE_EFFECT_STYLES, MENU_BAR_ITEM_STYLES, MENU_BAR_STYLES, topMenuStyles } from '@cloudbeaver/plugin-top-app-bar';
import { MENU_APP_ADMINISTRATION_ACTIONS } from './MENU_APP_ADMINISTRATION_ACTIONS';
export const AdministrationMenu = observer(function AdministrationMenu() {
const styles = useS(TopMenuWrapperStyles);
const menu = useMenu({ menu: MENU_APP_ADMINISTRATION_ACTIONS });
const { authenticated } = useService(AppAuthService);
@@ -25,9 +24,13 @@ export const AdministrationMenu = observer(function AdministrationMenu() {
return null;
}
return (
<div className={s(styles, { menuWrapper: true })}>
<MenuBar menu={menu} nestedMenuSettings={{ modal: true }} />
</div>
return styled(MENU_BAR_STYLES)(
<menu-wrapper>
<MenuBar
menu={menu}
style={[topMenuStyles, MENU_BAR_ITEM_STYLES, topMenuStyles, MENU_BAR_DISABLE_EFFECT_STYLES]}
nestedMenuSettings={{ modal: true }}
/>
</menu-wrapper>,
);
});
@@ -5,13 +5,17 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Placeholder, TopAppBar } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { TopNavBar } from '@cloudbeaver/plugin-top-app-bar';
import { AdministrationTopAppBarService } from './AdministrationTopAppBarService';
export function AdministrationTopAppBar() {
const administrationTopAppBarService = useService(AdministrationTopAppBarService);
return <TopNavBar container={administrationTopAppBarService.placeholder} />;
return (
<TopAppBar>
<Placeholder container={administrationTopAppBarService.placeholder} />
</TopAppBar>
);
}
@@ -17,7 +17,7 @@ const styles = css`
composes: theme-background-surface theme-text-on-surface theme-border-color-background from global;
position: sticky;
top: 0;
height: 32px;
height: 48px;
display: flex;
padding: 0 16px;
align-items: center;
@@ -26,7 +26,7 @@ const styles = css`
composes: theme-background-surface theme-text-on-surface theme-border-color-background from global;
position: relative;
border: solid 1px;
padding: 8px;
padding: 16px 24px;
}
p {
@@ -5,8 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { lazy } from 'react';
import { AdministrationScreenService } from '@cloudbeaver/core-administration';
import { EAdminPermission } from '@cloudbeaver/core-authentication';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
@@ -14,12 +12,11 @@ import { PermissionsService } from '@cloudbeaver/core-root';
import { ScreenService } from '@cloudbeaver/core-routing';
import { DATA_CONTEXT_MENU, MenuBaseItem, MenuService } from '@cloudbeaver/core-view';
import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu';
import { AppStateMenu } from '@cloudbeaver/plugin-top-app-bar';
import { AdministrationMenu } from './AdministrationMenu/AdministrationMenu';
import { AdministrationTopAppBarService } from './AdministrationScreen/AdministrationTopAppBar/AdministrationTopAppBarService';
const AdministrationMenu = lazy(() => import('./AdministrationMenu/AdministrationMenu').then(m => ({ default: m.AdministrationMenu })));
const AppStateMenu = lazy(() => import('@cloudbeaver/plugin-top-app-bar').then(m => ({ default: m.AppStateMenu })));
@injectable()
export class PluginBootstrap extends Bootstrap {
constructor(
@@ -33,6 +33,11 @@ const tabsStyles = css`
flex-shrink: 0;
align-items: center;
}
Tab {
height: 46px !important;
text-transform: uppercase;
font-weight: 500 !important;
}
`;
const topBarStyles = css`
@@ -40,7 +45,7 @@ const topBarStyles = css`
composes: theme-border-color-background theme-background-secondary theme-text-on-secondary from global;
position: relative;
display: flex;
padding-top: 8px;
padding-top: 16px;
&:before {
content: '';
@@ -58,14 +63,14 @@ const topBarStyles = css`
configuration-top-bar-actions {
display: flex;
align-items: center;
padding: 0 16px;
padding: 0 24px;
gap: 16px;
}
configuration-status-message {
composes: theme-typography--caption from global;
height: 24px;
padding: 0 8px;
padding: 0 16px;
display: flex;
align-items: center;
gap: 8px;
@@ -29,7 +29,7 @@ import { CreateAuthConfigurationService } from './CreateAuthConfigurationService
const loaderStyle = css`
ExceptionMessage {
padding: 16px;
padding: 24px;
}
`;
@@ -23,9 +23,9 @@ const styles = css`
}
title-bar {
composes: theme-border-color-background from global;
composes: theme-border-color-background theme-typography--headline6 from global;
box-sizing: border-box;
padding: 8px;
padding: 16px 24px;
align-items: center;
display: flex;
font-weight: 400;
@@ -25,9 +25,9 @@ const styles = css`
}
title-bar {
composes: theme-border-color-background from global;
composes: theme-border-color-background theme-typography--headline6 from global;
box-sizing: border-box;
padding: 8px;
padding: 16px 24px;
align-items: center;
display: flex;
font-weight: 400;
@@ -22,6 +22,11 @@ const tabsStyles = css`
flex-shrink: 0;
align-items: center;
}
Tab {
height: 46px !important;
text-transform: uppercase;
font-weight: 500 !important;
}
`;
const topBarStyles = css`
@@ -29,7 +34,7 @@ const topBarStyles = css`
composes: theme-border-color-background theme-background-secondary theme-text-on-secondary from global;
position: relative;
display: flex;
padding-top: 8px;
padding-top: 16px;
&:before {
content: '';
@@ -47,7 +52,7 @@ const topBarStyles = css`
top-bar-actions {
display: flex;
align-items: center;
padding: 0 16px;
padding: 0 24px;
gap: 16px;
}
@@ -58,7 +63,7 @@ const topBarStyles = css`
status-message {
composes: theme-typography--caption from global;
height: 24px;
padding: 0 8px;
padding: 0 16px;
display: flex;
align-items: center;
gap: 8px;
@@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { Tab, TabTitle } from '@cloudbeaver/core-ui';
import { Tab } from '@cloudbeaver/core-ui';
interface Props {
className?: string;
@@ -22,7 +22,7 @@ export const MetaParameterInfoTab = observer<Props>(function MetaParameterInfoTa
return styled(styles)(
<Tab tabId="info" style={style} className={className}>
<TabTitle>{translate('plugin_user_profile_info')}</TabTitle>
{translate('plugin_user_profile_info')}
</Tab>,
);
});
@@ -23,9 +23,9 @@ const styles = css`
}
title-bar {
composes: theme-border-color-background from global;
composes: theme-border-color-background theme-typography--headline6 from global;
box-sizing: border-box;
padding: 8px;
padding: 16px 24px;
align-items: center;
display: flex;
font-weight: 400;
@@ -33,6 +33,11 @@ const tabsStyles = css`
flex-shrink: 0;
align-items: center;
}
Tab {
height: 46px !important;
text-transform: uppercase;
font-weight: 500 !important;
}
`;
const topBarStyles = css`
@@ -40,7 +45,7 @@ const topBarStyles = css`
composes: theme-border-color-background theme-background-secondary theme-text-on-secondary from global;
position: relative;
display: flex;
padding-top: 8px;
padding-top: 16px;
&:before {
content: '';
@@ -58,14 +63,14 @@ const topBarStyles = css`
team-top-bar-actions {
display: flex;
align-items: center;
padding: 0 16px;
padding: 0 24px;
gap: 16px;
}
team-status-message {
composes: theme-typography--caption from global;
height: 24px;
padding: 0 8px;
padding: 0 16px;
display: flex;
align-items: center;
gap: 8px;

Some files were not shown because too many files have changed in this diff Show More