mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge pull request #297 from dbeaver/feat/form-new-design
Feat/form new design
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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';
|
||||
|
||||
import { composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
export const BASE_CONTAINERS_STYLES = composes(
|
||||
css`
|
||||
Group {
|
||||
composes: theme-background-surface from global;
|
||||
}
|
||||
ColoredContainer {
|
||||
composes: theme-background-secondary from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
Container, ColoredContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&[horizontal] {
|
||||
flex-direction: row;
|
||||
}
|
||||
&[wrap] {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
&[overflow] {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Group {
|
||||
box-sizing: border-box;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
Grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(min-content, 1fr);
|
||||
grid-auto-rows: max-content;
|
||||
&[horizontal] {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
&[center] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
& > [itemMax] {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
}
|
||||
|
||||
GroupTitle {
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 400;
|
||||
padding-left: 12px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
Container, ColoredContainer, Group, Grid {
|
||||
flex-grow: 1;
|
||||
&[small] {
|
||||
max-width: 250px;
|
||||
}
|
||||
&[medium] {
|
||||
max-width: 450px;
|
||||
}
|
||||
&[large] {
|
||||
max-width: 650px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { ILayoutSizeProps } from './ILayoutContainerProps';
|
||||
|
||||
interface Props extends ILayoutSizeProps {
|
||||
className?: string;
|
||||
horizontal?: boolean;
|
||||
wrap?: boolean;
|
||||
overflow?: boolean;
|
||||
}
|
||||
|
||||
export const ColoredContainer: React.FC<Props> = function ColoredContainer({ children, className }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { ILayoutSizeProps } from './ILayoutContainerProps';
|
||||
|
||||
interface Props extends ILayoutSizeProps {
|
||||
className?: string;
|
||||
horizontal?: boolean;
|
||||
wrap?: boolean;
|
||||
overflow?: boolean;
|
||||
}
|
||||
|
||||
export const Container: React.FC<Props> = function Container({ children, className }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { ILayoutSizeProps } from './ILayoutContainerProps';
|
||||
|
||||
interface Props extends ILayoutSizeProps {
|
||||
className?: string;
|
||||
horizontal?: boolean;
|
||||
center?: boolean;
|
||||
}
|
||||
|
||||
export const Grid: React.FC<Props> = function Grid({ children, className }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { ILayoutSizeProps } from './ILayoutContainerProps';
|
||||
|
||||
interface Props extends ILayoutSizeProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Group: React.FC<Props> = function Group({ children, className }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const GroupTitle: React.FC<Props> = function GroupTitle({ children, className }) {
|
||||
return (
|
||||
<h2 className={className}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface ILayoutSizeProps {
|
||||
small?: boolean;
|
||||
medium?: boolean;
|
||||
large?: boolean;
|
||||
}
|
||||
|
||||
export interface IGridItemsLayoutProps {
|
||||
itemMax?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 styled, { css } from 'reshadow';
|
||||
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { baseFormControlStylesNew } from '../baseFormControlStylesNew';
|
||||
import { isControlPresented } from '../isControlPresented';
|
||||
import { Checkbox, CheckboxType, CheckboxControlledProps, CheckboxObjectProps } from './Checkbox';
|
||||
|
||||
const fieldCheckboxStyles = css`
|
||||
Checkbox {
|
||||
margin: -10px;
|
||||
}
|
||||
field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
field-label {
|
||||
padding-left: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({
|
||||
checked: checkedControlled,
|
||||
children,
|
||||
className,
|
||||
autoHide,
|
||||
...rest
|
||||
}: CheckboxControlledProps | CheckboxObjectProps<any, any>) {
|
||||
const styles = useStyles(baseFormControlStylesNew, fieldCheckboxStyles);
|
||||
|
||||
if (autoHide && !isControlPresented(rest.name, rest.state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<field className={className} as="div">
|
||||
<Checkbox
|
||||
{...rest}
|
||||
checked={checkedControlled}
|
||||
/>
|
||||
<field-label as="div">{children}</field-label>
|
||||
</field>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 {
|
||||
useLayoutEffect, useCallback, useState, useRef, useContext
|
||||
} from 'react';
|
||||
import {
|
||||
useMenuState,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuButton
|
||||
} from 'reakit/Menu';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { IconButton } from '../IconButton';
|
||||
import { Icon } from '../Icons/Icon';
|
||||
import { baseFormControlStylesNew } from './baseFormControlStylesNew';
|
||||
import { FormContext } from './FormContext';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
Menu {
|
||||
composes: theme-text-on-surface theme-background-surface from global;
|
||||
}
|
||||
MenuItem {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
field input {
|
||||
margin: 0;
|
||||
}
|
||||
field-label {
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
input {
|
||||
padding-right: 20px;
|
||||
}
|
||||
MenuButton {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
composes: theme-typography--caption theme-elevation-z3 from global;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
padding: 4px 0;
|
||||
z-index: 999;
|
||||
|
||||
& MenuItem {
|
||||
background: transparent;
|
||||
display: block;
|
||||
padding: 4px 36px;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
Icon {
|
||||
height: 16px;
|
||||
display: block;
|
||||
}
|
||||
MenuButton Icon {
|
||||
transform: rotate(90deg);
|
||||
|
||||
&[|focus] {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
input-box {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSelect' | 'name' | 'value'> & {
|
||||
propertyName?: string;
|
||||
items: TValue[];
|
||||
searchable?: boolean;
|
||||
keySelector: (item: TValue) => TKey;
|
||||
valueSelector: (item: TValue) => string;
|
||||
onSwitch?: (state: boolean) => void;
|
||||
};
|
||||
|
||||
type ControlledProps<TKey, TValue> = BaseProps<TKey, TValue> & {
|
||||
name?: string;
|
||||
value?: string;
|
||||
onSelect?: (value: TKey, name: string | undefined, prev: TKey) => void;
|
||||
onChange?: (value: string, name: string | undefined) => any;
|
||||
state?: never;
|
||||
};
|
||||
|
||||
type ObjectProps<TValue, TKey extends keyof TState, TState> = BaseProps<TState[TKey], TValue> & {
|
||||
name: TKey;
|
||||
state: TState;
|
||||
onSelect?: (value: TState[TKey], name: TKey | undefined, prev: TState[TKey]) => void;
|
||||
onChange?: (value: string, name: TKey | undefined) => any;
|
||||
value?: never;
|
||||
};
|
||||
|
||||
interface ComboboxType {
|
||||
<TKey, TValue>(props: ControlledProps<TKey, TValue>): JSX.Element;
|
||||
<TValue, TKey extends keyof TState, TState>(props: ObjectProps<TValue, TKey, TState>): JSX.Element;
|
||||
}
|
||||
|
||||
export const ComboboxNew: ComboboxType = observer(function ComboboxNew({
|
||||
value: controlledValue,
|
||||
name,
|
||||
state,
|
||||
propertyName,
|
||||
items,
|
||||
children,
|
||||
title,
|
||||
className,
|
||||
searchable,
|
||||
readOnly,
|
||||
disabled,
|
||||
keySelector = v => v,
|
||||
valueSelector = v => v,
|
||||
onChange = () => {},
|
||||
onSelect,
|
||||
onSwitch,
|
||||
...rest
|
||||
}: ControlledProps<any, any> | ObjectProps<any, any, any>) {
|
||||
const context = useContext(FormContext);
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const menu = useMenuState({
|
||||
placement: 'bottom-end',
|
||||
currentId: null,
|
||||
gutter: 4,
|
||||
});
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
let value: string | number | readonly string[] | undefined = controlledValue;
|
||||
|
||||
if (state) {
|
||||
if (name in state) {
|
||||
value = state[name];
|
||||
} else if (rest.defaultValue !== undefined) {
|
||||
value = rest.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
onChange(value, name);
|
||||
setSearchValue(value);
|
||||
},
|
||||
[name, onChange]
|
||||
);
|
||||
|
||||
const handleRemove = useCallback(
|
||||
() => {
|
||||
menu.hide();
|
||||
if (state) {
|
||||
state[name] = null;
|
||||
}
|
||||
if (onSelect) {
|
||||
onSelect(null, name, value);
|
||||
}
|
||||
if (context) {
|
||||
context.onChange(null, name);
|
||||
}
|
||||
setSearchValue('');
|
||||
},
|
||||
[value, state, name, menu, context, onSelect]
|
||||
);
|
||||
|
||||
const handleMenuSelect = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
menu.hide();
|
||||
const id = event.currentTarget.id;
|
||||
if (state) {
|
||||
state[name] = id;
|
||||
}
|
||||
if (onSelect) {
|
||||
onSelect(id, name, value);
|
||||
}
|
||||
if (context) {
|
||||
context.onChange(id, name);
|
||||
}
|
||||
},
|
||||
[value, state, name, menu, context, onSelect]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => onSwitch?.(menu.visible), [onSwitch, menu.visible]);
|
||||
|
||||
const selectedItem = items.find(item => keySelector(item) === value);
|
||||
const filteredItems = items.filter(
|
||||
item => selectedItem || !searchValue || valueSelector(item).toUpperCase().includes(searchValue.toUpperCase())
|
||||
);
|
||||
|
||||
if (ref.current === document.activeElement && !selectedItem && searchValue) {
|
||||
menu.show();
|
||||
}
|
||||
|
||||
return styled(useStyles(baseFormControlStylesNew, styles))(
|
||||
<field as="div" className={className}>
|
||||
<field-label title={title} as='label'>{children}</field-label>
|
||||
<input-box as="div">
|
||||
<input
|
||||
ref={ref}
|
||||
role='new'
|
||||
name={name}
|
||||
title={title}
|
||||
value={selectedItem ? valueSelector(selectedItem) : searchValue}
|
||||
readOnly={readOnly}
|
||||
onChange={handleChange}
|
||||
{...rest}
|
||||
/>
|
||||
{(selectedItem && !readOnly && searchable) && (
|
||||
<IconButton type="button" name="reject" viewBox="0 0 11 11" onClick={handleRemove} />
|
||||
)}
|
||||
<MenuButton {...menu} disabled={readOnly || disabled}><Icon name="arrow" viewBox="0 0 16 16" {...use({ focus: menu.visible })} /></MenuButton>
|
||||
<Menu
|
||||
{...menu}
|
||||
aria-label={propertyName}
|
||||
unstable_initialFocusRef={ref}
|
||||
unstable_finalFocusRef={ref}
|
||||
>
|
||||
{filteredItems.map(item => (
|
||||
<MenuItem key={keySelector(item)} id={keySelector(item)} type='button' {...menu} onClick={handleMenuSelect}>
|
||||
{valueSelector(item)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</input-box>
|
||||
</field>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { useCallback, useContext } from 'react';
|
||||
import styled, { use, css } from 'reshadow';
|
||||
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutContainerProps';
|
||||
import { baseFormControlStylesNew } from './baseFormControlStylesNew';
|
||||
import { FormContext } from './FormContext';
|
||||
import { isControlPresented } from './isControlPresented';
|
||||
|
||||
const INPUT_FIELD_STYLES = css`
|
||||
field-label {
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'name' | 'value'> & ILayoutSizeProps & {
|
||||
description?: string;
|
||||
mod?: 'surface';
|
||||
};
|
||||
|
||||
type ControlledProps = BaseProps & {
|
||||
name?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string, name?: string) => any;
|
||||
state?: never;
|
||||
autoHide?: never;
|
||||
};
|
||||
|
||||
type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
|
||||
name: TKey;
|
||||
state: TState;
|
||||
onChange?: (value: string, name: TKey) => any;
|
||||
autoHide?: boolean;
|
||||
value?: never;
|
||||
};
|
||||
|
||||
interface InputFieldType {
|
||||
(props: ControlledProps): React.ReactElement<any, any> | null;
|
||||
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): React.ReactElement<any, any> | null;
|
||||
}
|
||||
|
||||
export const InputFieldNew: InputFieldType = observer(function InputFieldNew({
|
||||
name,
|
||||
value: valueControlled,
|
||||
required,
|
||||
state,
|
||||
children,
|
||||
className,
|
||||
description,
|
||||
mod,
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
autoHide,
|
||||
onChange,
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>) {
|
||||
const styles = useStyles(baseFormControlStylesNew, INPUT_FIELD_STYLES);
|
||||
const context = useContext(FormContext);
|
||||
|
||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (state) {
|
||||
state[name] = event.target.value;
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(event.target.value, name);
|
||||
}
|
||||
if (context) {
|
||||
context.onChange(event.target.value, name);
|
||||
}
|
||||
}, [state, name, context, onChange]);
|
||||
|
||||
const value = state ? state[name] : valueControlled;
|
||||
|
||||
if (autoHide && !isControlPresented(name, state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<field as="div" className={className} {...use({ small, medium, large })}>
|
||||
<field-label as='label'>{children} {required && '*'}</field-label>
|
||||
<input
|
||||
role='new'
|
||||
{...rest}
|
||||
name={name}
|
||||
value={value ?? ''}
|
||||
onChange={handleChange}
|
||||
{...use({ mod })}
|
||||
required={required}
|
||||
/>
|
||||
{description && (
|
||||
<field-description as='div'>
|
||||
{description}
|
||||
</field-description>
|
||||
)}
|
||||
</field>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { useCallback, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { baseFormControlStylesNew } from './baseFormControlStylesNew';
|
||||
import { FormContext } from './FormContext';
|
||||
import { isControlPresented } from './isControlPresented';
|
||||
|
||||
const switchStyles = composes(
|
||||
css`
|
||||
switch-control {
|
||||
composes: theme-switch from global;
|
||||
}
|
||||
switch-control-track {
|
||||
composes: theme-switch__track from global;
|
||||
}
|
||||
switch-input {
|
||||
composes: theme-switch_native-control from global;
|
||||
}
|
||||
switch-control-underlay {
|
||||
composes: theme-switch__thumb-underlay from global;
|
||||
}
|
||||
switch-control-thumb {
|
||||
composes: theme-switch__thumb from global;
|
||||
}
|
||||
radio-ripple {
|
||||
composes: theme-radio_ripple from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
field-label {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
const switchMod = {
|
||||
primary: composes(
|
||||
css`
|
||||
switch-control {
|
||||
composes: theme-switch_primary from global;
|
||||
}
|
||||
`
|
||||
),
|
||||
};
|
||||
|
||||
const switchState = {
|
||||
disabled: composes(
|
||||
css`
|
||||
switch-control {
|
||||
composes: theme-switch--disabled from global;
|
||||
}
|
||||
`
|
||||
),
|
||||
checked: composes(
|
||||
css`
|
||||
switch-control {
|
||||
composes: theme-switch--checked from global;
|
||||
}
|
||||
`
|
||||
),
|
||||
};
|
||||
|
||||
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'value' | 'checked'> & {
|
||||
mod?: Array<keyof typeof switchMod>;
|
||||
label?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type ControlledProps = BaseProps & {
|
||||
checked?: boolean;
|
||||
onChange?: (value: boolean, name?: string) => any;
|
||||
|
||||
state?: never;
|
||||
autoHide?: never;
|
||||
};
|
||||
|
||||
type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
|
||||
name: TKey;
|
||||
state: TState;
|
||||
onChange?: (value: boolean, name: TKey) => any;
|
||||
autoHide?: boolean;
|
||||
|
||||
checked?: never;
|
||||
};
|
||||
|
||||
interface SwitchType {
|
||||
(props: ControlledProps): React.ReactElement<any, any> | null;
|
||||
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): React.ReactElement<any, any> | null;
|
||||
}
|
||||
|
||||
export const SwitchNew: SwitchType = observer(function SwitchNew({
|
||||
name,
|
||||
id,
|
||||
label,
|
||||
description,
|
||||
state,
|
||||
checked: checkedControlled,
|
||||
className,
|
||||
children,
|
||||
onChange,
|
||||
mod = [],
|
||||
autoHide,
|
||||
disabled,
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>) {
|
||||
const context = useContext(FormContext);
|
||||
const checked = state ? state[name] : checkedControlled;
|
||||
const styles = useStyles(
|
||||
baseFormControlStylesNew,
|
||||
switchStyles,
|
||||
...mod.map(mod => switchMod[mod]),
|
||||
disabled && switchState.disabled,
|
||||
checked && switchState.checked
|
||||
);
|
||||
|
||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (state) {
|
||||
state[name] = event.target.checked;
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(event.target.checked, name);
|
||||
}
|
||||
if (context) {
|
||||
context.onChange(event.target.checked, name);
|
||||
}
|
||||
}, [state, name, context, onChange]);
|
||||
|
||||
if (autoHide && !isControlPresented(name, state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<field as="div" className={className}>
|
||||
<field-label as="div">{children}</field-label>
|
||||
<switch-control as='div'>
|
||||
<switch-control-track as='div' />
|
||||
<switch-control-underlay as='div'>
|
||||
<switch-control-thumb as='div' />
|
||||
<switch-input
|
||||
as='input'
|
||||
{...rest}
|
||||
type="checkbox"
|
||||
id={id}
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</switch-control-underlay>
|
||||
</switch-control>
|
||||
<label htmlFor={id}>{label}</label>
|
||||
<field-description as='div'>{description}</field-description>
|
||||
</field>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { useCallback, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutContainerProps';
|
||||
import { baseFormControlStylesNew } from './baseFormControlStylesNew';
|
||||
import { FormContext } from './FormContext';
|
||||
|
||||
const styles = css`
|
||||
textarea {
|
||||
line-height: 19px;
|
||||
}
|
||||
field-label {
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
type BaseProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'> & ILayoutSizeProps & {
|
||||
mod?: 'surface';
|
||||
};
|
||||
|
||||
type ControlledProps = BaseProps & {
|
||||
name?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string, name?: string) => any;
|
||||
state?: never;
|
||||
};
|
||||
|
||||
type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
|
||||
name: TKey;
|
||||
state: TState;
|
||||
onChange?: (value: string, name: TKey) => any;
|
||||
value?: never;
|
||||
};
|
||||
|
||||
interface TextareaType {
|
||||
(props: ControlledProps): JSX.Element;
|
||||
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): JSX.Element;
|
||||
}
|
||||
|
||||
export const TextareaNew: TextareaType = observer(function TextareaNew({
|
||||
name,
|
||||
value: controlledValue,
|
||||
state,
|
||||
children,
|
||||
className,
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
mod,
|
||||
onChange = () => {},
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>) {
|
||||
const context = useContext(FormContext);
|
||||
|
||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
if (state) {
|
||||
state[name] = event.target.value;
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(event.target.value, name);
|
||||
}
|
||||
if (context) {
|
||||
context.onChange(event.target.value, name);
|
||||
}
|
||||
}, [state, name, onChange]);
|
||||
|
||||
const value = state ? state[name] : controlledValue;
|
||||
|
||||
return styled(useStyles(baseFormControlStylesNew, styles))(
|
||||
<field as="div" className={className} {...use({ small, medium, large })}>
|
||||
<field-label as='label'>{children}</field-label>
|
||||
<textarea
|
||||
{...rest}
|
||||
role='new'
|
||||
value={value ?? ''}
|
||||
name={name}
|
||||
onChange={handleChange}
|
||||
{...use({ mod })}
|
||||
/>
|
||||
</field>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 baseFormControlStylesNew = css`
|
||||
field {
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
min-height: 54px;
|
||||
max-width: 100%;
|
||||
&[|small] {
|
||||
max-width: 250px;
|
||||
}
|
||||
&[|medium] {
|
||||
max-width: 450px;
|
||||
}
|
||||
&[|large] {
|
||||
max-width: 650px;
|
||||
}
|
||||
}
|
||||
|
||||
field-label {
|
||||
composes: theme-typography--body1 from global;
|
||||
box-sizing: border-box;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
field-description {
|
||||
composes: theme-typography--caption from global;
|
||||
padding-top: 8px;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { useCallback } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { FieldCheckboxNew } from '../FormControls/Checkboxes/FieldCheckboxNew';
|
||||
import { ComboboxNew } from '../FormControls/ComboboxNew';
|
||||
import { FormFieldDescription } from '../FormControls/FormFieldDescription';
|
||||
import { InputFieldNew } from '../FormControls/InputFieldNew';
|
||||
import { isControlPresented } from '../FormControls/isControlPresented';
|
||||
import { Link } from '../Link';
|
||||
import { TextPlaceholder } from '../TextPlaceholder';
|
||||
import { formStyles } from './formStyles';
|
||||
|
||||
const RESERVED_KEYWORDS = ['no', 'off', 'new-password'];
|
||||
|
||||
interface RenderFieldProps {
|
||||
property: ObjectPropertyInfo;
|
||||
state: Record<string, any>;
|
||||
editable?: boolean;
|
||||
autofillToken?: string;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
autoHide?: boolean;
|
||||
showRememberTip?: boolean;
|
||||
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
|
||||
property,
|
||||
state,
|
||||
editable = true,
|
||||
autofillToken = '',
|
||||
disabled,
|
||||
readOnly,
|
||||
autoHide,
|
||||
showRememberTip,
|
||||
onFocus,
|
||||
}) {
|
||||
const href = property.features.includes('href');
|
||||
const password = property.features.includes('password');
|
||||
const checkbox = property.dataType === 'Boolean';
|
||||
const combobox = property.validValues && property.validValues.length > 0;
|
||||
let description: string | undefined;
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<FormFieldDescription label={property.displayName} raw>
|
||||
<Link href={state[property.id!]} target='_blank' rel='noopener noreferrer'>{property.description}</Link>
|
||||
</FormFieldDescription>
|
||||
);
|
||||
}
|
||||
|
||||
if (!editable) {
|
||||
if (autoHide && !isControlPresented(property.id!, state)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FormFieldDescription label={property.displayName} raw>
|
||||
{state[property.id!]}
|
||||
</FormFieldDescription>
|
||||
);
|
||||
}
|
||||
|
||||
if (showRememberTip && password && property.value) {
|
||||
description = 'Password saved';
|
||||
}
|
||||
|
||||
if (checkbox) {
|
||||
return (
|
||||
<FieldCheckboxNew
|
||||
name={property.id!}
|
||||
state={state}
|
||||
checkboxLabel={property.displayName}
|
||||
title={property.description}
|
||||
disabled={disabled}
|
||||
mod='surface'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (combobox) {
|
||||
return (
|
||||
<ComboboxNew
|
||||
name={property.id!}
|
||||
state={state}
|
||||
items={property.validValues!}
|
||||
keySelector={value => value}
|
||||
valueSelector={value => value}
|
||||
defaultValue={property.defaultValue}
|
||||
title={property.description}
|
||||
disabled={disabled}
|
||||
>
|
||||
{property.displayName ?? ''}
|
||||
</ComboboxNew>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputFieldNew
|
||||
type={password ? 'password' : 'text'}
|
||||
name={property.id!}
|
||||
state={state}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
autoHide={autoHide}
|
||||
autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`}
|
||||
mod='surface'
|
||||
onFocus={onFocus}
|
||||
>
|
||||
{property.displayName}
|
||||
</InputFieldNew>
|
||||
);
|
||||
});
|
||||
|
||||
interface ObjectPropertyFormProps {
|
||||
properties: ObjectPropertyInfo[] | undefined;
|
||||
state: Record<string, string | number>;
|
||||
editable?: boolean;
|
||||
autofillToken?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
autoHide?: boolean;
|
||||
showRememberTip?: boolean;
|
||||
onFocus?: (name: string) => void;
|
||||
}
|
||||
|
||||
export const ObjectPropertyInfoFormNew: React.FC<ObjectPropertyFormProps> = observer(function ObjectPropertyInfoFormNew({
|
||||
properties,
|
||||
state,
|
||||
editable = true,
|
||||
autofillToken = '',
|
||||
className,
|
||||
disabled,
|
||||
readOnly,
|
||||
autoHide,
|
||||
showRememberTip,
|
||||
onFocus,
|
||||
}) {
|
||||
const style = useStyles(formStyles);
|
||||
|
||||
const handleFocus = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onFocus) {
|
||||
onFocus(e.target.name);
|
||||
}
|
||||
}, [onFocus]);
|
||||
|
||||
if (!properties || properties.length === 0) {
|
||||
return <TextPlaceholder>Properties empty</TextPlaceholder>;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<form-body as='div' className={className}>
|
||||
{properties.map(property => (
|
||||
<RenderField
|
||||
key={property.id}
|
||||
property={property}
|
||||
state={state}
|
||||
editable={editable}
|
||||
autofillToken={autofillToken}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
autoHide={autoHide}
|
||||
showRememberTip={showRememberTip}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
))}
|
||||
</form-body>
|
||||
);
|
||||
});
|
||||
@@ -5,6 +5,8 @@ export * from './layout/AppLogo';
|
||||
export * from './layout/TopAppBar';
|
||||
export * from './Loader/Loader';
|
||||
export * from './ObjectPropertyInfoForm/ObjectPropertyInfoForm';
|
||||
export * from './ObjectPropertyInfoForm/ObjectPropertyInfoFormNew';
|
||||
|
||||
export * from './Placeholder/Placeholder';
|
||||
export * from './Placeholder/PlaceholderContainer';
|
||||
export * from './PropertiesTable';
|
||||
@@ -24,14 +26,23 @@ export * from './Tree/TreeNode/TreeNodeStyles';
|
||||
export * from './Button';
|
||||
export * from './FormControls/Checkboxes/Checkbox';
|
||||
export * from './FormControls/Checkboxes/FieldCheckbox';
|
||||
export * from './FormControls/Checkboxes/FieldCheckboxNew';
|
||||
export * from './FormControls/Checkboxes/CheckboxMarkup';
|
||||
export * from './FormControls/FormBox';
|
||||
export * from './Containers/BASE_CONTAINERS_STYLES';
|
||||
export * from './Containers/Container';
|
||||
export * from './Containers/Grid';
|
||||
export * from './Containers/Group';
|
||||
export * from './Containers/GroupTitle';
|
||||
export * from './Containers/ColoredContainer';
|
||||
export * from './FormControls/FormBoxElement';
|
||||
export * from './FormControls/Combobox';
|
||||
export * from './FormControls/ComboboxNew';
|
||||
export * from './FormControls/FormContext';
|
||||
export * from './FormControls/FormFieldDescription';
|
||||
export * from './FormControls/FormGroup';
|
||||
export * from './FormControls/InputField';
|
||||
export * from './FormControls/InputFieldNew';
|
||||
export * from './FormControls/InputGroup';
|
||||
export * from './FormControls/Radio';
|
||||
export * from './FormControls/RadioGroup';
|
||||
@@ -39,7 +50,9 @@ export * from './FormControls/RadioGroupContext';
|
||||
export * from './FormControls/ShadowInput';
|
||||
export * from './FormControls/SubmittingForm';
|
||||
export * from './FormControls/Switch';
|
||||
export * from './FormControls/SwitchNew';
|
||||
export * from './FormControls/Textarea';
|
||||
export * from './FormControls/TextareaNew';
|
||||
export * from './ErrorMessage';
|
||||
export * from './ExceptionMessage';
|
||||
export * from './IconButton';
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 styled from 'reshadow';
|
||||
|
||||
import { BASE_CONTAINERS_STYLES, Grid, InputFieldNew } from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import type { ConnectionConfig } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
interface ParametersFormProps {
|
||||
config: ConnectionConfig;
|
||||
disabled?: boolean;
|
||||
embedded?: boolean;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ParametersFormNew = observer(function ParametersFormNew({
|
||||
config,
|
||||
embedded,
|
||||
disabled,
|
||||
readOnly,
|
||||
}: ParametersFormProps) {
|
||||
const translate = useTranslate();
|
||||
|
||||
return styled(useStyles(BASE_CONTAINERS_STYLES))(
|
||||
<Grid>
|
||||
{!embedded && (
|
||||
<Grid horizontal>
|
||||
<InputFieldNew
|
||||
type="text"
|
||||
name="host"
|
||||
state={config}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
large
|
||||
required
|
||||
>
|
||||
{translate('customConnection_custom_host')}
|
||||
</InputFieldNew>
|
||||
<InputFieldNew
|
||||
type="number"
|
||||
name="port"
|
||||
state={config}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
>
|
||||
{translate('customConnection_custom_port')}
|
||||
</InputFieldNew>
|
||||
</Grid>
|
||||
)}
|
||||
<InputFieldNew
|
||||
type="text"
|
||||
name="databaseName"
|
||||
state={config}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
full
|
||||
>
|
||||
{translate('customConnection_custom_database')}
|
||||
</InputFieldNew>
|
||||
</Grid>
|
||||
);
|
||||
});
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
@mixin form-controls() {
|
||||
input {
|
||||
input:not([role="new"]) {
|
||||
@include mdc-theme-prop(color, on-secondary, false);
|
||||
@include mdc-theme-prop(background-color, secondary, false);
|
||||
box-sizing: border-box;
|
||||
@@ -28,12 +28,50 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&[use|mod=surface] {
|
||||
&[use|mod="surface"] {
|
||||
@include mdc-theme-prop(color, on-surface, false);
|
||||
@include mdc-theme-prop(background-color, surface, false);
|
||||
}
|
||||
}
|
||||
|
||||
textarea[role="new"] {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
input[role="new"],
|
||||
textarea[role="new"] {
|
||||
@include mdc-theme-prop(color, on-secondary, false);
|
||||
@include mdc-theme-prop(background-color, input-background, false);
|
||||
box-sizing: border-box;
|
||||
padding: 9px 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
border: 2px solid;
|
||||
outline: none;
|
||||
border-radius: 3px;
|
||||
@include mdc-theme-prop(border-color, background, false);
|
||||
&:focus {
|
||||
@include mdc-theme-prop(border-color, primary, false);
|
||||
}
|
||||
|
||||
&:global([type="checkbox"]) {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
&:global([readonly]) {
|
||||
@include mdc-theme-prop(border-color, input-border-readonly, false);
|
||||
@include mdc-theme-prop(background-color, input-background-readonly, false);
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:global([disabled]) {
|
||||
@include mdc-theme-prop(border-color, input-border-disabled, false);
|
||||
@include mdc-theme-prop(background-color, input-background-disabled, false);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
line-height: 15px;
|
||||
@@ -54,7 +92,7 @@
|
||||
font-size: 14px;
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px $input-border-color;
|
||||
border: solid 1px $input-border;
|
||||
@include mdc-theme-prop(color, on-surface, false);
|
||||
@include mdc-theme-prop(background-color, surface, false);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,13 @@ $mdc-theme-error: #e94948;
|
||||
$color-positive: #01cca3;
|
||||
$color-on-positive: #fff;
|
||||
$color-negative: #fd1d48;
|
||||
$input-border-color: #585958;
|
||||
|
||||
$input-border: #585958;
|
||||
$input-border-disabled: darken($input-border, 10%);
|
||||
$input-border-readonly: darken($input-border, 5%);
|
||||
$input-background: $mdc-theme-secondary;
|
||||
$input-background-disabled: $input-background;
|
||||
$input-background-readonly: $input-background;
|
||||
|
||||
$mdc-theme-text-colors: (
|
||||
dark: (
|
||||
@@ -26,17 +32,17 @@ $mdc-theme-text-colors: (
|
||||
secondary: rgba(white, 0.7),
|
||||
hint: rgba(white, 0.5),
|
||||
disabled: rgba(white, 0.5),
|
||||
icon: rgba(white, 0.5)
|
||||
icon: rgba(white, 0.5),
|
||||
),
|
||||
light: (
|
||||
primary: rgba(black, 0.87),
|
||||
secondary: rgba(black, 0.54),
|
||||
hint: rgba(black, 0.38),
|
||||
disabled: rgba(black, 0.38),
|
||||
icon: rgba(black, 0.38)
|
||||
)
|
||||
icon: rgba(black, 0.38),
|
||||
),
|
||||
);
|
||||
|
||||
$theme-name: 'dark';
|
||||
$theme-name: "dark";
|
||||
|
||||
@import 'mixins';
|
||||
@import "mixins";
|
||||
|
||||
@@ -17,8 +17,14 @@ $mdc-theme-background: #dedede;
|
||||
$color-positive: #01cca3;
|
||||
$color-on-positive: #fff;
|
||||
$color-negative: #fd1d48;
|
||||
$input-border-color: #dedede;
|
||||
|
||||
$theme-name: 'light';
|
||||
$input-border: #dedede;
|
||||
$input-border-disabled: transparent;
|
||||
$input-border-readonly: transparent;
|
||||
$input-background: $mdc-theme-surface;
|
||||
$input-background-disabled: $mdc-theme-secondary;
|
||||
$input-background-readonly: $mdc-theme-secondary;
|
||||
|
||||
@import 'mixins';
|
||||
$theme-name: "light";
|
||||
|
||||
@import "mixins";
|
||||
|
||||
@@ -10,7 +10,12 @@ $mdc-theme-extra-property-values: (
|
||||
positive: $color-positive,
|
||||
on-positive: $color-on-positive,
|
||||
negative: $color-negative,
|
||||
input-border-color: $input-border-color,
|
||||
input-border: $input-border,
|
||||
input-border-readonly: $input-border-readonly,
|
||||
input-border-disabled: $input-border-disabled,
|
||||
input-background: $input-background,
|
||||
input-background-disabled: $input-background-disabled,
|
||||
input-background-readonly: $input-background-readonly,
|
||||
);
|
||||
|
||||
$mdc-theme-property-values: map-merge($mdc-theme-property-values, $mdc-theme-extra-property-values);
|
||||
|
||||
+14
-24
@@ -8,28 +8,16 @@
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useDatabaseObjectInfo } from '@cloudbeaver/core-app';
|
||||
import { Loader, TextPlaceholder } from '@cloudbeaver/core-blocks';
|
||||
import { ColoredContainer, Grid, Group, Loader, TextPlaceholder } from '@cloudbeaver/core-blocks';
|
||||
import { BASE_CONTAINERS_STYLES } from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ObjectProperty } from './ObjectProperty';
|
||||
|
||||
const tabStyles = css`
|
||||
properties {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
type ObjectPropertiesProps = PropsWithChildren<{
|
||||
objectId: string;
|
||||
}>;
|
||||
@@ -38,8 +26,8 @@ export const ObjectProperties = observer(function ObjectProperties({
|
||||
objectId,
|
||||
}: ObjectPropertiesProps) {
|
||||
const translate = useTranslate();
|
||||
const style = useStyles(tabStyles);
|
||||
const { dbObject, isLoading } = useDatabaseObjectInfo(objectId);
|
||||
const styles = useStyles(BASE_CONTAINERS_STYLES);
|
||||
|
||||
if (!dbObject?.properties && isLoading) {
|
||||
return <Loader />;
|
||||
@@ -49,13 +37,15 @@ export const ObjectProperties = observer(function ObjectProperties({
|
||||
return <TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<properties as="div">
|
||||
<container as="div">
|
||||
{dbObject.properties.map(v => (
|
||||
<ObjectProperty key={v.id} objectProperty={v} />
|
||||
))}
|
||||
</container>
|
||||
</properties>
|
||||
return styled(styles)(
|
||||
<ColoredContainer overflow>
|
||||
<Group>
|
||||
<Grid large>
|
||||
{dbObject.properties.map(v => (
|
||||
<ObjectProperty key={v.id} objectProperty={v} />
|
||||
))}
|
||||
</Grid>
|
||||
</Group>
|
||||
</ColoredContainer>
|
||||
);
|
||||
});
|
||||
|
||||
+27
-25
@@ -7,24 +7,11 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import {
|
||||
ObjectPropertyCheckbox,
|
||||
ObjectPropertyInput,
|
||||
ObjectPropertyProps
|
||||
} from '../formControls';
|
||||
import { filterProperty, matchType } from '../helpers';
|
||||
import { FieldCheckboxNew, InputFieldNew } from '@cloudbeaver/core-blocks';
|
||||
|
||||
const propertyStyles = css`
|
||||
property {
|
||||
composes: theme-typography--body2 from global;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px;
|
||||
width: 600px;
|
||||
}
|
||||
`;
|
||||
import type { ObjectPropertyProps } from '../formControls';
|
||||
import { additionalProps, filterProperty, getValue, matchType } from '../helpers';
|
||||
|
||||
export const ObjectProperty = observer(function ObjectProperty({
|
||||
objectProperty,
|
||||
@@ -34,15 +21,30 @@ export const ObjectProperty = observer(function ObjectProperty({
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(propertyStyles)(
|
||||
<property
|
||||
className={className}
|
||||
as="div"
|
||||
{...use({ checkbox: matchType(objectProperty.dataType) === 'checkbox' })}
|
||||
>
|
||||
return (
|
||||
<>
|
||||
{matchType(objectProperty.dataType) === 'checkbox'
|
||||
? <ObjectPropertyCheckbox objectProperty={objectProperty} />
|
||||
: <ObjectPropertyInput objectProperty={objectProperty} />}
|
||||
</property>
|
||||
? (
|
||||
<FieldCheckboxNew
|
||||
className={className}
|
||||
name={objectProperty.id}
|
||||
value={getValue(objectProperty.value)}
|
||||
readOnly
|
||||
{...additionalProps(objectProperty)}
|
||||
>
|
||||
{objectProperty.displayName}
|
||||
</FieldCheckboxNew>
|
||||
)
|
||||
: (
|
||||
<InputFieldNew
|
||||
className={className}
|
||||
name={objectProperty.id}
|
||||
value={getValue(objectProperty.value)}
|
||||
readOnly
|
||||
{...additionalProps(objectProperty)}
|
||||
>{objectProperty.displayName}
|
||||
</InputFieldNew>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user