mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat: migrate form-controls to css modules (#2041)
* feat: migrate form-controls to css modules * fix: checkbox field styles * fix: remove old field styles * fix: styles bugs * CB-3773 fix: styles * CB-3773 fix: field description styles * CB-3773 fix: radio button styles --------- Co-authored-by: Alexey <wrouds@gmail.com> Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexey
mr-anton-t
parent
4344dce700
commit
fe2c7b76be
@@ -1,2 +1,66 @@
|
||||
.checkbox {
|
||||
composes: theme-checkbox from global;
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
.checkboxInput {
|
||||
composes: theme-checkbox_native-control from global;
|
||||
}
|
||||
.checkboxBackground {
|
||||
composes: theme-checkbox__background from global;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
.checkboxCheckmark {
|
||||
composes: theme-checkbox__checkmark from global;
|
||||
}
|
||||
.checkboxCheckmarkPath {
|
||||
composes: theme-checkbox__checkmark-path from global;
|
||||
}
|
||||
.checkboxMixedmark {
|
||||
composes: theme-checkbox__mixedmark from global;
|
||||
}
|
||||
.checkboxRipple {
|
||||
composes: theme-checkbox__ripple from global;
|
||||
}
|
||||
.checkboxContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
}
|
||||
.checkboxLabel {
|
||||
composes: theme-typography--body2 from global;
|
||||
cursor: pointer;
|
||||
}
|
||||
.checkboxCaption {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
}
|
||||
|
||||
.primary {
|
||||
composes: theme-checkbox_primary from global;
|
||||
}
|
||||
.surface {
|
||||
composes: theme-checkbox_surface from global;
|
||||
}
|
||||
.small {
|
||||
composes: theme-checkbox_small from global;
|
||||
}
|
||||
|
||||
.small {
|
||||
&.checkboxContainer {
|
||||
& .checkbox {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
& .checkboxBackground {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
composes: theme-checkbox--disabled from global;
|
||||
}
|
||||
|
||||
.checked {
|
||||
composes: theme-checkbox--checked from global;
|
||||
}
|
||||
|
||||
@@ -6,103 +6,19 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
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';
|
||||
|
||||
const checkboxStyles = css`
|
||||
checkbox {
|
||||
composes: theme-checkbox from global;
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
checkbox-input {
|
||||
composes: theme-checkbox_native-control from global;
|
||||
}
|
||||
checkbox-background {
|
||||
composes: theme-checkbox__background from global;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
checkbox-checkmark {
|
||||
composes: theme-checkbox__checkmark from global;
|
||||
}
|
||||
checkbox-checkmark-path {
|
||||
composes: theme-checkbox__checkmark-path from global;
|
||||
}
|
||||
checkbox-mixedmark {
|
||||
composes: theme-checkbox__mixedmark from global;
|
||||
}
|
||||
checkbox-ripple {
|
||||
composes: theme-checkbox__ripple from global;
|
||||
}
|
||||
checkbox-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
}
|
||||
checkbox-label {
|
||||
composes: theme-typography--body2 from global;
|
||||
cursor: pointer;
|
||||
}
|
||||
checkbox-caption {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
}
|
||||
`;
|
||||
|
||||
const checkboxMod: Record<CheckboxMod, any> = {
|
||||
primary: css`
|
||||
checkbox {
|
||||
composes: theme-checkbox_primary from global;
|
||||
}
|
||||
`,
|
||||
surface: css`
|
||||
checkbox {
|
||||
composes: theme-checkbox_surface from global;
|
||||
}
|
||||
`,
|
||||
small: css`
|
||||
checkbox {
|
||||
composes: theme-checkbox_small from global;
|
||||
}
|
||||
checkbox-container {
|
||||
& checkbox {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
& checkbox-background {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const checkboxState = {
|
||||
disabled: css`
|
||||
checkbox {
|
||||
composes: theme-checkbox--disabled from global;
|
||||
}
|
||||
`,
|
||||
checked: css`
|
||||
checkbox {
|
||||
composes: theme-checkbox--checked from global;
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
interface ICheckboxMarkupProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'style'> {
|
||||
label?: string;
|
||||
caption?: string;
|
||||
indeterminate?: boolean;
|
||||
ripple?: boolean;
|
||||
mod?: CheckboxMod[];
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export { CheckboxMarkupStyles };
|
||||
@@ -115,7 +31,6 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
|
||||
title,
|
||||
mod = ['primary'],
|
||||
ripple = true,
|
||||
style,
|
||||
readOnly,
|
||||
caption,
|
||||
...rest
|
||||
@@ -129,32 +44,40 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
|
||||
}
|
||||
});
|
||||
|
||||
return styled(
|
||||
useStyles(
|
||||
checkboxStyles,
|
||||
...(mod || []).map(mod => checkboxMod[mod]),
|
||||
rest.disabled && checkboxState.disabled,
|
||||
rest.checked && checkboxState.checked,
|
||||
style,
|
||||
),
|
||||
)(
|
||||
<checkbox-container className={className} title={title}>
|
||||
<checkbox className={s(styles, { checkbox: true })}>
|
||||
<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">
|
||||
<checkbox-checkmark-path as="path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59" />
|
||||
</checkbox-checkmark>
|
||||
<checkbox-mixedmark />
|
||||
</checkbox-background>
|
||||
{ripple && <checkbox-ripple />}
|
||||
</checkbox>
|
||||
return (
|
||||
<div className={s(styles, { checkboxContainer: true, small: mod.includes('small') }, className)} title={title}>
|
||||
<div
|
||||
className={s(styles, {
|
||||
checkbox: true,
|
||||
disabled: rest.disabled,
|
||||
checked: rest.checked,
|
||||
primary: mod.includes('primary'),
|
||||
surface: mod.includes('surface'),
|
||||
small: mod.includes('small'),
|
||||
})}
|
||||
>
|
||||
<input
|
||||
ref={checkboxRef}
|
||||
className={s(styles, { checkboxInput: true })}
|
||||
type="checkbox"
|
||||
{...rest}
|
||||
disabled={rest.disabled || readOnly}
|
||||
id={id || rest.name}
|
||||
/>
|
||||
<div className={s(styles, { checkboxBackground: true })}>
|
||||
<svg className={s(styles, { checkboxCheckmark: true })} viewBox="0 0 24 24">
|
||||
<path className={s(styles, { checkboxCheckmarkPath: true })} fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59" />
|
||||
</svg>
|
||||
<div className={s(styles, { checkboxMixedmark: true })} />
|
||||
</div>
|
||||
{ripple && <div className={s(styles, { checkboxRipple: true })} />}
|
||||
</div>
|
||||
{label && (id || rest.name) && (
|
||||
<checkbox-label as="label" htmlFor={id || rest.name}>
|
||||
<label className={s(styles, { checkboxLabel: true })} htmlFor={id || rest.name}>
|
||||
{label}
|
||||
{caption && <checkbox-caption>{caption}</checkbox-caption>}
|
||||
</checkbox-label>
|
||||
{caption && <div className={s(styles, { checkboxCaption: true })}>{caption}</div>}
|
||||
</label>
|
||||
)}
|
||||
</checkbox-container>,
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,36 +5,39 @@
|
||||
* 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 { filterLayoutFakeProps, getLayoutProps } from '../../Containers/filterLayoutFakeProps';
|
||||
import elementsSizeStyles from '../../Containers/shared/ElementsSize.m.css';
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import formControlStyles from '../FormControl.m.css';
|
||||
import { Field } from '../Field';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { isControlPresented } from '../isControlPresented';
|
||||
import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox';
|
||||
import fieldCheckboxStyles from './FieldCheckbox.m.css';
|
||||
|
||||
export const FieldCheckbox: CheckboxType = function FieldCheckbox({
|
||||
export const FieldCheckbox: CheckboxType = observer(function FieldCheckbox({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
const checkboxProps = filterLayoutFakeProps(rest);
|
||||
const styles = useS(elementsSizeStyles, formControlStyles, fieldCheckboxStyles);
|
||||
const styles = useS(fieldCheckboxStyles);
|
||||
|
||||
if (checkboxProps.autoHide && !isControlPresented(checkboxProps.name, checkboxProps.state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="field" className={s(styles, { field: true, ...layoutProps }, className)}>
|
||||
<Checkbox {...(checkboxProps as CheckboxBaseProps & ICheckboxControlledProps)} className={styles.checkbox} />
|
||||
<Field {...layoutProps} className={s(styles, { field: true }, className)}>
|
||||
<Checkbox {...(checkboxProps as CheckboxBaseProps & ICheckboxControlledProps)} className={s(styles, { checkbox: true })} />
|
||||
{children && (
|
||||
<label data-testid="field-label" htmlFor={checkboxProps.id || checkboxProps.name} title={checkboxProps.title} className={styles.fieldLabel}>
|
||||
<FieldLabel htmlFor={checkboxProps.id || checkboxProps.name} title={checkboxProps.title} className={s(styles, { fieldLabel: true })}>
|
||||
{children}
|
||||
</label>
|
||||
</FieldLabel>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { filterLayoutFakeProps } from '../../Containers/filterLayoutFakeProps';
|
||||
import elementsSizeStyles from '../../Containers/shared/ElementsSize.m.css';
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import formControlStyles from '../FormControl.m.css';
|
||||
import { Field } from '../Field';
|
||||
import { FieldDescription } from '../FieldDescription';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { isControlPresented } from '../isControlPresented';
|
||||
import type { ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox';
|
||||
import switchStyles from './Switch.m.css';
|
||||
@@ -64,14 +65,14 @@ export const Switch: SwitchType = observer(function Switch({
|
||||
onChange,
|
||||
});
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const styles = useS(elementsSizeStyles, formControlStyles, switchStyles, ...mod.map(mod => switchMod[mod]));
|
||||
const styles = useS(switchStyles, ...mod.map(mod => switchMod[mod]));
|
||||
|
||||
if (autoHide && !isControlPresented(name, state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="field" className={s(styles, { field: true }, className)} title={rest.title}>
|
||||
<Field data-testid="field" title={rest.title}>
|
||||
<div data-testid="switch-body" className={styles.switchBody}>
|
||||
<div data-testid="switch-control" className={s(styles, { switchControl: true, disabled: disabled, checked: checkboxState.checked })}>
|
||||
<div data-testid="switch-control-track" className={styles.switchControlTrack} />
|
||||
@@ -91,15 +92,11 @@ export const Switch: SwitchType = observer(function Switch({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor={id || value || name} data-testid="field-label" className={styles.fieldLabel}>
|
||||
<FieldLabel htmlFor={id || value || name} data-testid="field-label" className={styles.fieldLabel}>
|
||||
{children}
|
||||
</label>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
{description && (
|
||||
<div data-testid="field-description" className={styles.fieldDescription}>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{description && <FieldDescription>{description}</FieldDescription>}
|
||||
</Field>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -23,6 +23,15 @@
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
|
||||
.input {
|
||||
font-size: 12px;
|
||||
|
||||
&.select {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Menu, MenuButton, MenuItem, useMenuState } from 'reakit/Menu';
|
||||
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { getComputed } from '../getComputed';
|
||||
import { Icon } from '../Icon';
|
||||
import { IconOrImage } from '../IconOrImage';
|
||||
@@ -20,8 +19,10 @@ import { useTranslate } from '../localization/useTranslate';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import comboboxStyles from './Combobox.m.css';
|
||||
import { Field } from './Field';
|
||||
import { FieldDescription } from './FieldDescription';
|
||||
import { FieldLabel } from './FieldLabel';
|
||||
import { FormContext } from './FormContext';
|
||||
import formControlStyles from './FormControl.m.css';
|
||||
|
||||
type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSelect' | 'name' | 'value' | 'defaultValue'> &
|
||||
ILayoutSizeProps & {
|
||||
@@ -93,7 +94,7 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
const context = useContext(FormContext);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
|
||||
const styles = useS(elementsSizeStyles, formControlStyles, comboboxStyles);
|
||||
const styles = useS(comboboxStyles);
|
||||
|
||||
const menu = useMenuState({
|
||||
placement: 'bottom-end',
|
||||
@@ -255,17 +256,22 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="field" className={s(styles, { field: true, inline, ...layoutProps }, className)}>
|
||||
<Field {...layoutProps} className={s(styles, { field: true, inline }, className)}>
|
||||
{children && (
|
||||
<div title={title} data-testid="field-label" className={styles.fieldLabel}>
|
||||
<FieldLabel required={rest.required} title={title} className={s(styles, { fieldLabel: true })}>
|
||||
{children}
|
||||
{rest.required && ' *'}
|
||||
</div>
|
||||
</FieldLabel>
|
||||
)}
|
||||
<div data-testid="input-box" className={styles.inputBox}>
|
||||
<div className={s(styles, { inputBox: true })}>
|
||||
{(icon || loading) && (
|
||||
<div data-testid="input-icon" className={styles.inputIcon}>
|
||||
{loading ? <Loader small fullSize /> : typeof icon === 'string' ? <IconOrImage icon={icon} className={styles.iconOrImage} /> : icon}
|
||||
<div className={s(styles, { inputIcon: true })}>
|
||||
{loading ? (
|
||||
<Loader small fullSize />
|
||||
) : typeof icon === 'string' ? (
|
||||
<IconOrImage icon={icon} className={s(styles, { iconOrImage: true })} />
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
@@ -291,13 +297,11 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
{...menu}
|
||||
ref={menuRef}
|
||||
aria-label={propertyName}
|
||||
// unstable_finalFocusRef={inputRef || undefined}
|
||||
// unstable_initialFocusRef={ref}
|
||||
className={styles.menu}
|
||||
className={s(styles, { menu: true })}
|
||||
modal
|
||||
>
|
||||
{!filteredItems.length ? (
|
||||
<MenuItem id="placeholder" disabled {...menu} className={styles.menuItem}>
|
||||
<MenuItem id="placeholder" disabled {...menu} className={s(styles, { menuItem: true })}>
|
||||
{translate('combobox_no_results_placeholder')}
|
||||
</MenuItem>
|
||||
) : (
|
||||
@@ -314,28 +318,22 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
title={title}
|
||||
{...menu}
|
||||
disabled={disabled}
|
||||
className={styles.menuItem}
|
||||
className={s(styles, { menuItem: true })}
|
||||
onClick={event => handleSelect(event.currentTarget.id)}
|
||||
>
|
||||
{iconSelector && (
|
||||
<div data-testid="item-icon" className={styles.itemIcon}>
|
||||
{icon && typeof icon === 'string' ? <IconOrImage icon={icon} className={styles.iconOrImage} /> : icon}
|
||||
<div data-testid="item-icon" className={s(styles, { itemIcon: true })}>
|
||||
{icon && typeof icon === 'string' ? <IconOrImage icon={icon} className={s(styles, { iconOrImage: true })} /> : icon}
|
||||
</div>
|
||||
)}
|
||||
<div data-testid="item-value" className={styles.itemValue}>
|
||||
{valueSelector(item)}
|
||||
</div>
|
||||
<div data-testid="item-value">{valueSelector(item)}</div>
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
{description && (
|
||||
<div data-testid="field-description" className={styles.fieldDescription}>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{description && <FieldDescription>{description}</FieldDescription>}
|
||||
</Field>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.field {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
|
||||
/* nested <Field> */
|
||||
& .field {
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import type { HTMLAttributes, PropsWithChildren } from 'react';
|
||||
|
||||
import { getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import fieldStyles from './Field.m.css';
|
||||
|
||||
type Props = ILayoutSizeProps &
|
||||
HTMLAttributes<HTMLDivElement> & {
|
||||
className?: string;
|
||||
};
|
||||
export const Field: React.FC<PropsWithChildren<Props>> = observer(function Field({ children, className, ...rest }) {
|
||||
const styles = useS(fieldStyles, elementsSizeStyles);
|
||||
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
|
||||
return (
|
||||
<div {...rest} className={s(styles, { ...layoutProps, field: true }, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
.fieldDescription {
|
||||
composes: theme-typography--caption from global;
|
||||
/*color: var(--theme-text-hint-on-background);*/
|
||||
box-sizing: border-box;
|
||||
padding-top: 4px;
|
||||
min-height: 24px;
|
||||
|
||||
&.invalid {
|
||||
color: var(--theme-negative);
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import fieldDescriptionStyles from './FieldDescription.m.css';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
invalid?: boolean;
|
||||
}
|
||||
export const FieldDescription: React.FC<PropsWithChildren<Props>> = observer(function FieldDescription({ children, className, invalid }) {
|
||||
const styles = useS(fieldDescriptionStyles);
|
||||
|
||||
return <div className={s(styles, { fieldDescription: true, invalid }, className)}>{children}</div>;
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
.fieldLabel {
|
||||
composes: theme-typography--body1 from global;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import type { LabelHTMLAttributes, PropsWithChildren } from 'react';
|
||||
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import fieldLabelStyles from './FieldLabel.m.css';
|
||||
|
||||
type Props = LabelHTMLAttributes<HTMLLabelElement> & {
|
||||
className?: string;
|
||||
title?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
export const FieldLabel: React.FC<PropsWithChildren<Props>> = observer(function FieldLabel({ children, className, required, ...rest }) {
|
||||
const styles = useS(fieldLabelStyles);
|
||||
|
||||
return (
|
||||
<label {...rest} className={s(styles, { fieldLabel: true }, className)}>
|
||||
{children}
|
||||
{required && ' *'}
|
||||
</label>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
.filterContainer {
|
||||
position: relative;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
}
|
||||
.inputField {
|
||||
display: none;
|
||||
width: 300px;
|
||||
&.max {
|
||||
width: 100%;
|
||||
}
|
||||
&.toggled {
|
||||
display: block;
|
||||
}
|
||||
|
||||
& .input {
|
||||
padding-right: 40px !important;
|
||||
}
|
||||
|
||||
}
|
||||
.iconButton {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
cursor: auto;
|
||||
|
||||
&.toggled {
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
}
|
||||
&.cross {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.toggleMode {
|
||||
composes: theme-background-primary theme-text-on-primary from global;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -7,74 +7,20 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { IconButton } from '../IconButton';
|
||||
import { s } from '../s';
|
||||
import { useFocus } from '../useFocus';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { useS } from '../useS';
|
||||
import filterStyle from './Filter.m.css';
|
||||
import { InputField } from './InputField';
|
||||
|
||||
const filterStyles = css`
|
||||
filter-container {
|
||||
position: relative;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
}
|
||||
InputField {
|
||||
display: none;
|
||||
width: 300px;
|
||||
&[|max] {
|
||||
width: 100%;
|
||||
}
|
||||
&[|toggled] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
IconButton {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
cursor: auto;
|
||||
|
||||
&[|toggled] {
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
}
|
||||
&[name='cross'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const toggleModeButtonStyle = css`
|
||||
IconButton {
|
||||
composes: theme-background-primary theme-text-on-primary from global;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const innerInputStyle = css`
|
||||
input {
|
||||
padding-right: 40px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
interface BaseProps {
|
||||
toggleMode?: boolean;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
max?: boolean;
|
||||
className?: string;
|
||||
style?: ComponentStyle;
|
||||
onToggle?: (status: boolean) => void;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
@@ -103,13 +49,12 @@ export const Filter = observer<ControlledProps | ObjectsProps<any, any>>(functio
|
||||
disabled,
|
||||
max,
|
||||
className,
|
||||
style,
|
||||
onFilter,
|
||||
onToggle,
|
||||
onKeyDown,
|
||||
onClick,
|
||||
}) {
|
||||
const styles = useStyles(filterStyles, style, toggleMode && toggleModeButtonStyle);
|
||||
const styles = useS(filterStyle);
|
||||
const [inputRef, ref] = useFocus<HTMLInputElement>({});
|
||||
const [toggled, setToggled] = useState(!toggleMode);
|
||||
|
||||
@@ -156,24 +101,28 @@ export const Filter = observer<ControlledProps | ObjectsProps<any, any>>(functio
|
||||
value = state[name];
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<filter-container className={className} onClick={onClick}>
|
||||
return (
|
||||
<div className={s(styles, { filterContainer: true }, className)} onClick={onClick}>
|
||||
<InputField
|
||||
ref={inputRef}
|
||||
style={[innerInputStyle, style]}
|
||||
className={s(styles, { inputField: true, max, toggled })}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={filter}
|
||||
onKeyDown={onKeyDown}
|
||||
{...use({ toggled, max })}
|
||||
/>
|
||||
{String(value) ? (
|
||||
<IconButton name="cross" disabled={disabled} onClick={() => filter('', name)} />
|
||||
<IconButton
|
||||
className={s(styles, { iconButton: true, cross: true, toggleMode })}
|
||||
name="cross"
|
||||
disabled={disabled}
|
||||
onClick={() => filter('', name)}
|
||||
/>
|
||||
) : (
|
||||
<IconButton name="search" disabled={disabled} onClick={toggle} {...use({ toggled })} />
|
||||
<IconButton className={s(styles, { iconButton: true, toggled, toggleMode })} name="search" disabled={disabled} onClick={toggle} />
|
||||
)}
|
||||
</filter-container>,
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,24 +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 styled, { css } from 'reshadow';
|
||||
|
||||
const styles = css`
|
||||
box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const FormBox: React.FC<React.PropsWithChildren<Props>> = function FormBox({ children, className }) {
|
||||
return styled(styles)(<box className={className}>{children}</box>);
|
||||
};
|
||||
@@ -1,32 +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 styled, { css, use } from 'reshadow';
|
||||
|
||||
const styles = css`
|
||||
box-element {
|
||||
flex-basis: 550px;
|
||||
flex-grow: 1;
|
||||
|
||||
&[|max] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
max?: boolean;
|
||||
}
|
||||
|
||||
export const FormBoxElement: React.FC<React.PropsWithChildren<Props>> = function FormBoxElement({ children, className, max }) {
|
||||
return styled(styles)(
|
||||
<box-element as="div" className={className} {...use({ max })}>
|
||||
{children}
|
||||
</box-element>,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
.fieldLabel {
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
white-space: initial;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.fieldDescription {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -5,33 +5,14 @@
|
||||
* 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 { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { baseFormControlStyles, baseValidFormControlStyles } from './baseFormControlStyles';
|
||||
|
||||
const style = css`
|
||||
field-label {
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
white-space: initial;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
field-description {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
import { Field } from './Field';
|
||||
import { FieldDescription } from './FieldDescription';
|
||||
import { FieldLabel } from './FieldLabel';
|
||||
import style from './FormFieldDescription.m.css';
|
||||
|
||||
interface Props extends ILayoutSizeProps {
|
||||
label?: string;
|
||||
@@ -48,13 +29,12 @@ export const FormFieldDescription: React.FC<React.PropsWithChildren<Props>> = fu
|
||||
}) {
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const styles = useStyles(baseFormControlStyles, baseValidFormControlStyles, style);
|
||||
const sizeStyles = useS(elementsSizeStyles);
|
||||
const styles = useS(style);
|
||||
|
||||
return styled(styles)(
|
||||
<field title={title} className={s(sizeStyles, { ...layoutProps }, className)} {...rest}>
|
||||
{label && <field-label as="label">{label}</field-label>}
|
||||
<field-description>{children}</field-description>
|
||||
</field>,
|
||||
return (
|
||||
<Field title={title} className={className} {...rest} {...layoutProps}>
|
||||
{label && <FieldLabel className={s(styles, { fieldLabel: true })}>{label}</FieldLabel>}
|
||||
<FieldDescription className={s(styles, { fieldDescription: true })}>{children}</FieldDescription>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +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 styled, { css } from 'reshadow';
|
||||
|
||||
const styles = css`
|
||||
group {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const FormGroup: React.FC<React.PropsWithChildren<Props>> = function FormGroup({ children, className }) {
|
||||
return styled(styles)(<group className={className}>{children}</group>);
|
||||
};
|
||||
@@ -45,3 +45,7 @@
|
||||
.input:not(:only-child) {
|
||||
padding-right: 32px !important;
|
||||
}
|
||||
.input {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useCallback, useContext, useLayoutEffect, useRef, useState } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { isNotNullDefined } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { Icon } from '../Icon';
|
||||
import { Loader } from '../Loader/Loader';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
@@ -23,9 +20,10 @@ import { useCombinedHandler } from '../useCombinedHandler';
|
||||
import { useCombinedRef } from '../useCombinedRef';
|
||||
import { useS } from '../useS';
|
||||
import { useStateDelay } from '../useStateDelay';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { Field } from './Field';
|
||||
import { FieldDescription } from './FieldDescription';
|
||||
import { FieldLabel } from './FieldLabel';
|
||||
import { FormContext } from './FormContext';
|
||||
import formControlStyles from './FormControl.m.css';
|
||||
import inputFieldStyle from './InputField.m.css';
|
||||
import { isControlPresented } from './isControlPresented';
|
||||
import { useCapsLockTracker } from './useCapsLockTracker';
|
||||
@@ -36,9 +34,7 @@ type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' |
|
||||
loading?: boolean;
|
||||
description?: string;
|
||||
labelTooltip?: string;
|
||||
mod?: 'surface';
|
||||
ref?: React.ForwardedRef<HTMLInputElement>;
|
||||
style?: ComponentStyle;
|
||||
canShowPassword?: boolean;
|
||||
onCustomCopy?: () => void;
|
||||
icon?: React.ReactElement;
|
||||
@@ -73,7 +69,6 @@ export const InputField: InputFieldType = observer<ControlledProps | ObjectProps
|
||||
forwardRef(function InputField(
|
||||
{
|
||||
name,
|
||||
style,
|
||||
value: valueControlled,
|
||||
defaultValue,
|
||||
required,
|
||||
@@ -86,7 +81,6 @@ export const InputField: InputFieldType = observer<ControlledProps | ObjectProps
|
||||
loading,
|
||||
description,
|
||||
labelTooltip,
|
||||
mod,
|
||||
autoHide,
|
||||
canShowPassword = true,
|
||||
onChange,
|
||||
@@ -103,8 +97,7 @@ export const InputField: InputFieldType = observer<ControlledProps | ObjectProps
|
||||
const translate = useTranslate();
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const propStyles = useStyles(style);
|
||||
const styles = useS(inputFieldStyle, formControlStyles, elementsSizeStyles);
|
||||
const styles = useS(inputFieldStyle);
|
||||
const context = useContext(FormContext);
|
||||
loading = useStateDelay(loading ?? false, 300);
|
||||
|
||||
@@ -165,13 +158,12 @@ export const InputField: InputFieldType = observer<ControlledProps | ObjectProps
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(propStyles)(
|
||||
<div data-testid="field" className={s(styles, { ...layoutProps, field: true }, className)}>
|
||||
<div data-testid="field-label" title={labelTooltip || rest.title} className={styles.fieldLabel}>
|
||||
return (
|
||||
<Field {...layoutProps} className={s(styles, {}, className)}>
|
||||
<FieldLabel title={labelTooltip || rest.title} className={s(styles, { fieldLabel: true })} required={required}>
|
||||
{children}
|
||||
{required && ' *'}
|
||||
</div>
|
||||
<div data-testid="input-container" className={styles.inputContainer}>
|
||||
</FieldLabel>
|
||||
<div className={s(styles, { inputContainer: true })}>
|
||||
<input
|
||||
ref={mergedRef}
|
||||
{...rest}
|
||||
@@ -179,40 +171,35 @@ export const InputField: InputFieldType = observer<ControlledProps | ObjectProps
|
||||
name={name}
|
||||
value={uncontrolled ? undefined : value ?? ''}
|
||||
defaultValue={defaultValue}
|
||||
className={styles.input}
|
||||
className={s(styles, { input: true })}
|
||||
required={required}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...use({ mod })}
|
||||
required={required}
|
||||
/>
|
||||
{loading && (
|
||||
<div data-testid="loader-container" title={translate('ui_processing_loading')} className={styles.loaderContainer}>
|
||||
<div title={translate('ui_processing_loading')} className={s(styles, { loaderContainer: true })}>
|
||||
<Loader small />
|
||||
</div>
|
||||
)}
|
||||
{passwordType && canShowPassword && (
|
||||
<div data-testid="icon-container" title={translate('ui_reveal_password')} className={styles.iconContainer} onClick={revealPassword}>
|
||||
<div title={translate('ui_reveal_password')} className={styles.iconContainer} onClick={revealPassword}>
|
||||
<Icon name={passwordRevealed ? 'password-hide' : 'password-show'} viewBox="0 0 16 16" className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
{onCustomCopy && (
|
||||
<div data-testid="icon-container" title={translate('ui_copy_to_clipboard')} className={styles.iconContainer} onClick={onCustomCopy}>
|
||||
<div title={translate('ui_copy_to_clipboard')} className={styles.iconContainer} onClick={onCustomCopy}>
|
||||
<Icon name="copy" viewBox="0 0 32 32" className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
{icon && (
|
||||
<div data-testid="icon-container" className={styles.customIconContainer}>
|
||||
<div data-testid="icon-container" className={s(styles, { customIconContainer: true })}>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(description || passwordType) && (
|
||||
<div data-testid="field-description" className={s(styles, { fieldDescription: true, valid: !error, invalid: error })}>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
{(description || passwordType) && <FieldDescription invalid={error}>{description}</FieldDescription>}
|
||||
</Field>
|
||||
);
|
||||
}),
|
||||
) as InputFieldType;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.fieldLabel {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
.fieldLabel:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.fieldDescription {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.iconButton {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -7,51 +7,25 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { ReactNode, useContext, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { blobToData, bytesToSize } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { IconButton } from '../IconButton';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { s } from '../s';
|
||||
import { UploadArea } from '../UploadArea';
|
||||
import { useS } from '../useS';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { baseFormControlStyles, baseInvalidFormControlStyles, baseValidFormControlStyles } from './baseFormControlStyles';
|
||||
import { Field } from './Field';
|
||||
import { FieldDescription } from './FieldDescription';
|
||||
import { FieldLabel } from './FieldLabel';
|
||||
import { FormContext } from './FormContext';
|
||||
import inputFileTextContentStyles from './InputFileTextContent.m.css';
|
||||
|
||||
const DEFAULT_MAX_FILE_SIZE = 2048;
|
||||
|
||||
const INPUT_FILE_FIELD_STYLES = css`
|
||||
field-label {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
field-label:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
field-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
IconButton {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props<TState> extends ILayoutSizeProps {
|
||||
name: keyof TState;
|
||||
state: TState;
|
||||
@@ -80,14 +54,12 @@ export const InputFileTextContent: InputFileTextContentType = observer(function
|
||||
tooltip,
|
||||
required,
|
||||
fileName,
|
||||
style,
|
||||
maxFileSize = DEFAULT_MAX_FILE_SIZE,
|
||||
disabled,
|
||||
className,
|
||||
children,
|
||||
onChange,
|
||||
mapValue,
|
||||
...rest
|
||||
}: Props<Record<any, any>>) {
|
||||
const translate = useTranslate();
|
||||
const context = useContext(FormContext);
|
||||
@@ -95,10 +67,7 @@ export const InputFileTextContent: InputFileTextContentType = observer(function
|
||||
const [selected, setSelected] = useState<File | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const styles = useStyles(INPUT_FILE_FIELD_STYLES, baseFormControlStyles, style, error ? baseInvalidFormControlStyles : baseValidFormControlStyles);
|
||||
const sizeStyles = useS(elementsSizeStyles);
|
||||
const styles = useS(inputFileTextContentStyles);
|
||||
|
||||
const savedExternally = !!fileName && state[name] !== '';
|
||||
const saved = savedExternally || !!state[name];
|
||||
@@ -166,21 +135,20 @@ export const InputFileTextContent: InputFileTextContentType = observer(function
|
||||
}
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<field className={s(sizeStyles, { ...layoutProps }, className)}>
|
||||
<field-label title={labelTooltip}>
|
||||
return (
|
||||
<Field className={className}>
|
||||
<FieldLabel title={labelTooltip} required={required} className={s(styles, { fieldLabel: true })}>
|
||||
{children}
|
||||
{required && ' *'}
|
||||
</field-label>
|
||||
</FieldLabel>
|
||||
<UploadArea title={tooltip} disabled={disabled} accept={accept} reset onChange={handleChange}>
|
||||
<Button icon="/icons/import.svg" tag="div" mod={['outlined']} disabled={disabled}>
|
||||
{translate('ui_upload_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
<field-description>
|
||||
<FieldDescription className={s(styles, { fieldDescription: true })}>
|
||||
{description}
|
||||
{(selected || saved) && <IconButton disabled={disabled} name="cross" onClick={removeFile} />}
|
||||
</field-description>
|
||||
</field>,
|
||||
{(selected || saved) && <IconButton className={s(styles, { iconButton: true })} disabled={disabled} name="cross" onClick={removeFile} />}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
.fieldLabel {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
.fieldLabel:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.inputContainer {
|
||||
position: relative;
|
||||
}
|
||||
.tags {
|
||||
padding-top: 8px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,12 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useContext, useEffect, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { s } from '../s';
|
||||
import { Tag } from '../Tags/Tag';
|
||||
@@ -24,32 +22,13 @@ import { useCombinedHandler } from '../useCombinedHandler';
|
||||
import { useRefInherit } from '../useRefInherit';
|
||||
import { useS } from '../useS';
|
||||
import { useStateDelay } from '../useStateDelay';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { baseFormControlStyles, baseInvalidFormControlStyles, baseValidFormControlStyles } from './baseFormControlStyles';
|
||||
import { Field } from './Field';
|
||||
import { FieldDescription } from './FieldDescription';
|
||||
import { FieldLabel } from './FieldLabel';
|
||||
import { FormContext } from './FormContext';
|
||||
import InputFilesStyles from './InputFiles.m.css';
|
||||
import { isControlPresented } from './isControlPresented';
|
||||
|
||||
const INPUT_FIELD_STYLES = css`
|
||||
field-label {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
field-label:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
input-container {
|
||||
position: relative;
|
||||
}
|
||||
Tags {
|
||||
padding-top: 8px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'name' | 'value' | 'style'> &
|
||||
ILayoutSizeProps & {
|
||||
error?: boolean;
|
||||
@@ -57,9 +36,7 @@ type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' |
|
||||
description?: string;
|
||||
labelTooltip?: string;
|
||||
hideTags?: boolean;
|
||||
mod?: 'surface';
|
||||
ref?: React.Ref<HTMLInputElement>;
|
||||
style?: ComponentStyle;
|
||||
aggregate?: boolean;
|
||||
onDuplicate?: (files: File[]) => void;
|
||||
};
|
||||
@@ -89,7 +66,6 @@ export const InputFiles: InputFilesType = observer(
|
||||
forwardRef(function InputFiles(
|
||||
{
|
||||
name,
|
||||
style,
|
||||
value: valueControlled,
|
||||
required,
|
||||
state,
|
||||
@@ -100,7 +76,6 @@ export const InputFiles: InputFilesType = observer(
|
||||
description,
|
||||
labelTooltip,
|
||||
hideTags,
|
||||
mod,
|
||||
autoHide,
|
||||
aggregate,
|
||||
onChange,
|
||||
@@ -114,8 +89,7 @@ export const InputFiles: InputFilesType = observer(
|
||||
const ref = useRefInherit<HTMLInputElement>(refInherit);
|
||||
const [innerState, setInnerState] = useState<FileList | null>(null);
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(baseFormControlStyles, error ? baseInvalidFormControlStyles : baseValidFormControlStyles, INPUT_FIELD_STYLES, style);
|
||||
const sizeStyles = useS(elementsSizeStyles);
|
||||
const styles = useS(InputFilesStyles);
|
||||
const context = useContext(FormContext);
|
||||
loading = useStateDelay(loading ?? false, 300);
|
||||
|
||||
@@ -195,28 +169,27 @@ export const InputFiles: InputFilesType = observer(
|
||||
|
||||
const files = Array.from(value ?? []);
|
||||
|
||||
return styled(styles)(
|
||||
<field className={s(sizeStyles, { ...layoutProps }, className)}>
|
||||
<field-label title={labelTooltip || rest.title}>
|
||||
return (
|
||||
<Field {...layoutProps} className={className}>
|
||||
<FieldLabel title={labelTooltip || rest.title} required={required} className={s(styles, { fieldLabel: true })}>
|
||||
{children}
|
||||
{required && ' *'}
|
||||
</field-label>
|
||||
<input-container>
|
||||
<UploadArea ref={ref} {...rest} name={name} value={value} {...use({ mod })} required={required} onChange={handleChange}>
|
||||
</FieldLabel>
|
||||
<div className={s(styles, { inputContainer: true })}>
|
||||
<UploadArea ref={ref} {...rest} name={name} value={value} required={required} onChange={handleChange}>
|
||||
<Button icon="/icons/import.svg" tag="div" loading={loading} mod={['outlined']}>
|
||||
{translate(rest.multiple ? 'ui_upload_files' : 'ui_upload_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
{!hideTags && (
|
||||
<Tags>
|
||||
<Tags className={s(styles, { tags: true })}>
|
||||
{files.map((file, i) => (
|
||||
<Tag key={file.name} id={i} label={file.name} onRemove={removeFile} />
|
||||
))}
|
||||
</Tags>
|
||||
)}
|
||||
</input-container>
|
||||
{description && <field-description>{description}</field-description>}
|
||||
</field>,
|
||||
</div>
|
||||
{description && <FieldDescription invalid={error}>{description}</FieldDescription>}
|
||||
</Field>
|
||||
);
|
||||
}),
|
||||
) as InputFilesType;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
.radio {
|
||||
composes: theme-radio from global;
|
||||
}
|
||||
|
||||
.radioNoRipple {
|
||||
composes: theme-radio_no-ripple from global;
|
||||
}
|
||||
|
||||
.radioBackground {
|
||||
composes: theme-radio_background from global;
|
||||
}
|
||||
.input {
|
||||
composes: theme-radio_native-control from global;
|
||||
}
|
||||
.radioOuterCircle {
|
||||
composes: theme-radio_outer-circle from global;
|
||||
}
|
||||
.radioInnerCircle {
|
||||
composes: theme-radio_inner-circle from global;
|
||||
}
|
||||
.radioRipple {
|
||||
composes: theme-radio_ripple from global;
|
||||
}
|
||||
.field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.label {
|
||||
cursor: pointer;
|
||||
.disabled {
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
composes: theme-radio_primary from global;
|
||||
}
|
||||
|
||||
.small {
|
||||
composes: theme-radio_small from global;
|
||||
}
|
||||
|
||||
.field.menu {
|
||||
padding: 0;
|
||||
|
||||
& .radio {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
& .radioBackground {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
& .radioInnerCircle {
|
||||
border-width: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.disabledRadio {
|
||||
composes: theme-radio_disabled from global;
|
||||
}
|
||||
|
||||
.disabledInput {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
@@ -7,105 +7,19 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { baseFormControlStyles } from './baseFormControlStyles';
|
||||
import { Field } from './Field';
|
||||
import { FormContext } from './FormContext';
|
||||
import style from './Radio.m.css';
|
||||
import { RadioGroupContext } from './RadioGroupContext';
|
||||
|
||||
const radioStyles = css`
|
||||
radio {
|
||||
composes: theme-radio from global;
|
||||
}
|
||||
radio-background {
|
||||
composes: theme-radio_background from global;
|
||||
}
|
||||
input {
|
||||
composes: theme-radio_native-control from global;
|
||||
}
|
||||
radio-outer-circle {
|
||||
composes: theme-radio_outer-circle from global;
|
||||
}
|
||||
radio-inner-circle {
|
||||
composes: theme-radio_inner-circle from global;
|
||||
}
|
||||
radio-ripple {
|
||||
composes: theme-radio_ripple from global;
|
||||
}
|
||||
field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
padding: 7px 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
label {
|
||||
cursor: pointer;
|
||||
&[|disabled] {
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const radioMod = {
|
||||
primary: css`
|
||||
radio {
|
||||
composes: theme-radio_primary from global;
|
||||
}
|
||||
`,
|
||||
small: css`
|
||||
radio {
|
||||
composes: theme-radio_small from global;
|
||||
}
|
||||
`,
|
||||
menu: css`
|
||||
radio {
|
||||
composes: theme-radio_small from global;
|
||||
}
|
||||
field {
|
||||
padding: 0;
|
||||
|
||||
& radio {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
& radio-background {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
& radio-inner-circle {
|
||||
border-width: 7px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const noRippleStyles = css`
|
||||
radio {
|
||||
composes: theme-radio_no-ripple from global;
|
||||
}
|
||||
`;
|
||||
|
||||
const radioState = {
|
||||
disabled: css`
|
||||
radio {
|
||||
composes: theme-radio--disabled from global;
|
||||
}
|
||||
input {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'checked'> &
|
||||
ILayoutSizeProps & {
|
||||
mod?: Array<keyof typeof radioMod>;
|
||||
mod?: Array<'primary' | 'small' | 'menu'>;
|
||||
ripple?: boolean;
|
||||
};
|
||||
|
||||
@@ -142,9 +56,10 @@ export const Radio: RadioType = observer(function Radio({
|
||||
children,
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>) {
|
||||
const styles = useS(style);
|
||||
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const sizeStyles = useS(elementsSizeStyles);
|
||||
const formContext = useContext(FormContext);
|
||||
const context = useContext(RadioGroupContext);
|
||||
|
||||
@@ -184,27 +99,36 @@ export const Radio: RadioType = observer(function Radio({
|
||||
checked = state[name] === value;
|
||||
}
|
||||
|
||||
return styled(
|
||||
useStyles(
|
||||
baseFormControlStyles,
|
||||
radioStyles,
|
||||
...(mod || []).map(mod => radioMod[mod]),
|
||||
!ripple && noRippleStyles,
|
||||
rest.disabled && radioState.disabled,
|
||||
),
|
||||
)(
|
||||
<field className={s(sizeStyles, { ...layoutProps }, className)}>
|
||||
<radio>
|
||||
<input {...rest} type="radio" id={id} name={name} value={value ?? ''} checked={checked} onChange={handleChange} />
|
||||
<radio-background>
|
||||
<radio-outer-circle />
|
||||
<radio-inner-circle />
|
||||
</radio-background>
|
||||
{ripple && <radio-ripple />}
|
||||
</radio>
|
||||
<label {...use({ disabled: rest.disabled })} htmlFor={id}>
|
||||
return (
|
||||
<Field {...layoutProps} className={s(styles, { field: true, menu: mod?.includes('menu') }, className)}>
|
||||
<div
|
||||
className={s(styles, {
|
||||
radio: true,
|
||||
primary: mod?.includes('primary'),
|
||||
small: mod?.includes('small') || mod?.includes('menu'),
|
||||
disabledRadio: rest.disabled,
|
||||
radioNoRipple: !ripple,
|
||||
})}
|
||||
>
|
||||
<input
|
||||
{...rest}
|
||||
className={s(styles, { input: true, disabledInput: rest.disabled })}
|
||||
type="radio"
|
||||
id={id}
|
||||
name={name}
|
||||
value={value ?? ''}
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={s(styles, { radioBackground: true })}>
|
||||
<div className={s(styles, { radioOuterCircle: true })} />
|
||||
<div className={s(styles, { radioInnerCircle: true })} />
|
||||
</div>
|
||||
{ripple && <div className={s(styles, { radioRipple: true })} />}
|
||||
</div>
|
||||
<label className={s(styles, { label: true, disabled: rest.disabled })} htmlFor={id}>
|
||||
{children}
|
||||
</label>
|
||||
</field>,
|
||||
</Field>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.textarea {
|
||||
line-height: 19px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.field.embedded {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .textarea {
|
||||
border-radius: 0 !important;
|
||||
height: 100%;
|
||||
resize: none !important;
|
||||
}
|
||||
}
|
||||
.fieldLabel {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -7,52 +7,21 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import elementsSizeStyles from '../Containers/shared/ElementsSize.m.css';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { baseFormControlStyles, baseValidFormControlStyles } from './baseFormControlStyles';
|
||||
import { Field } from './Field';
|
||||
import { FieldDescription } from './FieldDescription';
|
||||
import { FieldLabel } from './FieldLabel';
|
||||
import { FormContext } from './FormContext';
|
||||
|
||||
const styles = css`
|
||||
textarea {
|
||||
line-height: 19px;
|
||||
}
|
||||
field[|embedded] {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& textarea {
|
||||
border-radius: 0 !important;
|
||||
height: 100%;
|
||||
resize: none !important;
|
||||
}
|
||||
}
|
||||
field-label {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
import textareaStyle from './Textarea.m.css';
|
||||
|
||||
type BaseProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange' | 'style'> &
|
||||
ILayoutSizeProps & {
|
||||
description?: string;
|
||||
labelTooltip?: string;
|
||||
mod?: 'surface';
|
||||
style?: ComponentStyle;
|
||||
embedded?: boolean;
|
||||
};
|
||||
|
||||
@@ -77,7 +46,6 @@ interface TextareaType {
|
||||
|
||||
export const Textarea: TextareaType = observer(function Textarea({
|
||||
name,
|
||||
style,
|
||||
value: controlledValue,
|
||||
state,
|
||||
required,
|
||||
@@ -85,14 +53,13 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
className,
|
||||
description,
|
||||
labelTooltip,
|
||||
mod,
|
||||
embedded,
|
||||
onChange = () => {},
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>) {
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const sizeStyles = useS(elementsSizeStyles);
|
||||
const styles = useS(textareaStyle);
|
||||
const context = useContext(FormContext);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@@ -112,14 +79,20 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
|
||||
const value = state ? state[name] : controlledValue;
|
||||
|
||||
return styled(useStyles(baseFormControlStyles, baseValidFormControlStyles, styles, style))(
|
||||
<field className={s(sizeStyles, { ...layoutProps }, className)} {...use({ embedded })}>
|
||||
<field-label title={labelTooltip || rest.title}>
|
||||
return (
|
||||
<Field {...layoutProps} className={s(styles, { field: true, embedded }, className)}>
|
||||
<FieldLabel className={s(styles, { fieldLabel: true })} title={labelTooltip || rest.title} required={required}>
|
||||
{children}
|
||||
{required && ' *'}
|
||||
</field-label>
|
||||
<textarea {...rest} value={value ?? ''} name={name} data-embedded={embedded} onChange={handleChange} {...use({ mod })} />
|
||||
{description && <field-description>{description}</field-description>}
|
||||
</field>,
|
||||
</FieldLabel>
|
||||
<textarea
|
||||
{...rest}
|
||||
className={s(styles, { textarea: true })}
|
||||
value={value ?? ''}
|
||||
name={name}
|
||||
data-embedded={embedded}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{description && <FieldDescription>{description}</FieldDescription>}
|
||||
</Field>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,57 +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 { css } from 'reshadow';
|
||||
|
||||
export const baseFormControlStyles = css`
|
||||
field {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
|
||||
& field {
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
field-label {
|
||||
composes: theme-typography--body1 from global;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
field-description {
|
||||
composes: theme-typography--caption from global;
|
||||
box-sizing: border-box;
|
||||
padding-top: 4px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
font-size: 12px;
|
||||
|
||||
&[|select] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const baseValidFormControlStyles = css`
|
||||
field-description {
|
||||
composes: theme-text-text-hint-on-light from global;
|
||||
}
|
||||
`;
|
||||
|
||||
export const baseInvalidFormControlStyles = css`
|
||||
field-description {
|
||||
composes: theme-text-negative from global;
|
||||
}
|
||||
`;
|
||||
-4
@@ -193,7 +193,6 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
state={state}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
mod="surface"
|
||||
className={className}
|
||||
>
|
||||
{property.displayName ?? ''}
|
||||
@@ -210,7 +209,6 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
mod="surface"
|
||||
className={className}
|
||||
>
|
||||
{property.displayName ?? ''}
|
||||
@@ -232,7 +230,6 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
readOnly={readOnly}
|
||||
autoHide={autoHide}
|
||||
autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`}
|
||||
mod="surface"
|
||||
className={className}
|
||||
canShowPassword={canShowPassword}
|
||||
onFocus={onFocus}
|
||||
@@ -254,7 +251,6 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`}
|
||||
mod="surface"
|
||||
className={className}
|
||||
canShowPassword={canShowPassword}
|
||||
onFocus={onFocus}
|
||||
|
||||
@@ -13,13 +13,17 @@ import styled from 'reshadow';
|
||||
import { Button } from '../Button';
|
||||
import ButtonStyles from '../Button.m.css';
|
||||
import { Filter } from '../FormControls/Filter';
|
||||
import FilterStyles from '../FormControls/Filter.m.css';
|
||||
import InputFieldStyles from '../FormControls/InputField.m.css';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { SContext, StyleRegistry } from '../SContext';
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
import type { IProperty } from './IProperty';
|
||||
import PropertiesTableFilterStyles from './PropertiesTableFilterStyles.m.css';
|
||||
import PropertiesTableInputStyles from './PropertiesTableInputStyles.m.css';
|
||||
import PropertiesTableAddButtonStyles from './PropertiesTableAddButtonStyles.m.css';
|
||||
import { PropertyItem } from './PropertyItem';
|
||||
import { PROPERTIES_FILTER_STYLES, PROPERTIES_TABLE_STYLES } from './styles';
|
||||
import { PROPERTIES_TABLE_STYLES } from './styles';
|
||||
|
||||
type PropertiesState = Record<string, string | null>;
|
||||
|
||||
@@ -36,6 +40,20 @@ interface Props {
|
||||
}
|
||||
|
||||
const registry: StyleRegistry = [
|
||||
[
|
||||
InputFieldStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [PropertiesTableInputStyles],
|
||||
},
|
||||
],
|
||||
[
|
||||
FilterStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [PropertiesTableFilterStyles],
|
||||
},
|
||||
],
|
||||
[
|
||||
ButtonStyles,
|
||||
{
|
||||
@@ -134,12 +152,13 @@ export const PropertiesTable = observer<Props>(function PropertiesTable(props) {
|
||||
<properties-header-name>
|
||||
<div>{translate('core_block_properties_table_name')}</div>
|
||||
{props.filterable ? (
|
||||
<Filter
|
||||
value={filterValue}
|
||||
placeholder={translate('core_block_properties_table_filter_name')}
|
||||
style={PROPERTIES_FILTER_STYLES}
|
||||
onFilter={setFilterValue}
|
||||
/>
|
||||
<SContext registry={registry}>
|
||||
<Filter
|
||||
value={filterValue}
|
||||
placeholder={translate('core_block_properties_table_filter_name')}
|
||||
onFilter={setFilterValue}
|
||||
/>
|
||||
</SContext>
|
||||
) : null}
|
||||
</properties-header-name>
|
||||
<properties-header-value>{translate('core_block_properties_table_value')}</properties-header-value>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
.iconButton {
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&.toggled {
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&.cross {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.input {
|
||||
composes: theme-background-surface from global;
|
||||
box-sizing: border-box;
|
||||
height: 24px;
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -58,36 +58,3 @@ export const PROPERTIES_TABLE_STYLES = css`
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PROPERTIES_FILTER_STYLES = css`
|
||||
input {
|
||||
composes: theme-background-surface from global;
|
||||
box-sizing: border-box;
|
||||
height: 24px;
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
InputField {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
IconButton {
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&[|toggled] {
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&[name='cross'] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.tableSelect {
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
}
|
||||
@@ -7,11 +7,13 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Checkbox } from '../FormControls/Checkboxes/Checkbox';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import { TableContext } from './TableContext';
|
||||
import style from './TableSelect.m.css';
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
@@ -20,14 +22,8 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const styles = css`
|
||||
Checkbox {
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TableSelect = observer<Props>(function TableSelect({ id, disabled, tooltip, className }) {
|
||||
const styles = useS(style);
|
||||
const tableContext = useContext(TableContext);
|
||||
const translate = useTranslate();
|
||||
|
||||
@@ -35,14 +31,14 @@ export const TableSelect = observer<Props>(function TableSelect({ id, disabled,
|
||||
throw new Error('Context must be provided');
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
return (
|
||||
<Checkbox
|
||||
id={id}
|
||||
className={className}
|
||||
className={s(styles, { tableSelect: true }, className)}
|
||||
title={tooltip || translate('ui_select_all')}
|
||||
disabled={disabled || !tableContext.state.selectableItems.length}
|
||||
checked={tableContext.state.tableSelected}
|
||||
onClick={tableContext.state.selectTable}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -131,8 +131,8 @@ export * from './FormControls/Checkboxes/CheckboxMarkup';
|
||||
export * from './FormControls/Checkboxes/Switch';
|
||||
export * from './FormControls/Checkboxes/useCheckboxState';
|
||||
export * from './FormControls/BASE_DROPDOWN_STYLES';
|
||||
export * from './FormControls/FormBox';
|
||||
export * from './FormControls/Filter';
|
||||
export { default as FilterStyles } from './FormControls/Filter.m.css';
|
||||
export * from './Fill';
|
||||
|
||||
export * from './Containers/Container';
|
||||
@@ -154,12 +154,11 @@ export * from './FolderExplorer/useFolderExplorer';
|
||||
export * from './Tags/Tag';
|
||||
export * from './Tags/Tags';
|
||||
|
||||
export * from './FormControls/FormBoxElement';
|
||||
export * from './FormControls/Combobox';
|
||||
export * from './FormControls/FormContext';
|
||||
export * from './FormControls/FormFieldDescription';
|
||||
export * from './FormControls/FormGroup';
|
||||
export * from './FormControls/InputField';
|
||||
export { default as InputFieldStyles } from './FormControls/InputField.m.css';
|
||||
export * from './FormControls/InputFiles';
|
||||
export * from './FormControls/InputFileTextContent';
|
||||
export * from './FormControls/Radio';
|
||||
|
||||
+1
-3
@@ -22,7 +22,7 @@ export const ServerConfigurationInfoForm = observer<Props>(function ServerConfig
|
||||
return (
|
||||
<Group form gap>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_server_info')}</GroupTitle>
|
||||
<InputField type="text" name="serverName" state={state.serverConfig} mod="surface" required medium>
|
||||
<InputField type="text" name="serverName" state={state.serverConfig} required medium>
|
||||
{translate('administration_configuration_wizard_configuration_server_name')}
|
||||
</InputField>
|
||||
<InputField
|
||||
@@ -31,7 +31,6 @@ export const ServerConfigurationInfoForm = observer<Props>(function ServerConfig
|
||||
name="serverURL"
|
||||
state={state.serverConfig}
|
||||
readOnly={serverConfigLoader.resource.distributed}
|
||||
mod="surface"
|
||||
required
|
||||
medium
|
||||
>
|
||||
@@ -42,7 +41,6 @@ export const ServerConfigurationInfoForm = observer<Props>(function ServerConfig
|
||||
type="number"
|
||||
name="sessionExpireTime"
|
||||
state={state.serverConfig}
|
||||
mod="surface"
|
||||
min={1}
|
||||
mapState={v => (v === 0 ? 60000 : v ?? 1800000) / 1000 / 60}
|
||||
mapValue={v => (v === undefined ? 30 : Number(v) || 1) * 1000 * 60}
|
||||
|
||||
+2
-2
@@ -20,10 +20,10 @@ export const ServerConfigurationAdminForm = observer<Props>(function ServerConfi
|
||||
return (
|
||||
<Group form gap medium>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_admin')}</GroupTitle>
|
||||
<InputField type="text" name="adminName" state={serverConfig} minLength={6} mod="surface" required tiny>
|
||||
<InputField type="text" name="adminName" state={serverConfig} minLength={6} required tiny>
|
||||
{translate('administration_configuration_wizard_configuration_admin_name')}
|
||||
</InputField>
|
||||
<InputField type="password" name="adminPassword" state={serverConfig} autoComplete="new-password" mod="surface" required tiny>
|
||||
<InputField type="password" name="adminPassword" state={serverConfig} autoComplete="new-password" required tiny>
|
||||
{translate('administration_configuration_wizard_configuration_admin_password')}
|
||||
</InputField>
|
||||
</Group>
|
||||
|
||||
+1
-3
@@ -44,7 +44,7 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
|
||||
return (
|
||||
<Container gap vertical>
|
||||
<GroupTitle keepSize>{translate('authentication_user_credentials')}</GroupTitle>
|
||||
<InputField type="text" name="userId" state={tabState.state} readOnly={editing} disabled={disabled} mod="surface" keepSize tiny required>
|
||||
<InputField type="text" name="userId" state={tabState.state} readOnly={editing} disabled={disabled} keepSize tiny required>
|
||||
{translate('authentication_user_name')}
|
||||
</InputField>
|
||||
{local && (
|
||||
@@ -57,7 +57,6 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
|
||||
placeholder={editing ? PASSWORD_PLACEHOLDER : ''}
|
||||
canShowPassword={tabState.state['password'] !== ''}
|
||||
disabled={disabled}
|
||||
mod="surface"
|
||||
required={!editing}
|
||||
keepSize
|
||||
tiny
|
||||
@@ -70,7 +69,6 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
|
||||
name="passwordRepeat"
|
||||
placeholder={editing ? PASSWORD_PLACEHOLDER : ''}
|
||||
disabled={disabled}
|
||||
mod="surface"
|
||||
required={!editing}
|
||||
canShowPassword
|
||||
keepSize
|
||||
|
||||
@@ -53,7 +53,6 @@ export const AuthProviderForm = observer<Props>(function AuthProviderForm({ prov
|
||||
disabled={authenticate}
|
||||
canShowPassword={false}
|
||||
autoComplete={`section-authentication section-${provider.id} ${parameter.id}`}
|
||||
mod="surface"
|
||||
>
|
||||
{parameter.displayName}
|
||||
</InputField>
|
||||
|
||||
+2
-2
@@ -53,10 +53,10 @@ export const NetworkHandlerAuthForm = observer<Props>(function NetworkHandlerAut
|
||||
</GroupTitle>
|
||||
{ssh && (
|
||||
<>
|
||||
<InputField type="text" name="userName" state={state} disabled={disabled} mod="surface">
|
||||
<InputField type="text" name="userName" state={state} disabled={disabled}>
|
||||
{translate(`connections_network_handler_${id}_user`, 'connections_network_handler_default_user')}
|
||||
</InputField>
|
||||
<InputField type="password" name="password" canShowPassword={false} state={state} disabled={disabled} mod="surface">
|
||||
<InputField type="password" name="password" canShowPassword={false} state={state} disabled={disabled}>
|
||||
{passwordLabel}
|
||||
</InputField>
|
||||
</>
|
||||
|
||||
@@ -236,7 +236,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
disabled={disabled}
|
||||
readOnly={readonly}
|
||||
autoComplete={`section-${config.driverId || 'driver'} section-jdbc`}
|
||||
mod="surface"
|
||||
>
|
||||
{translate('customConnection_url_JDBC')}
|
||||
</InputField>
|
||||
@@ -253,7 +252,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
</Group>
|
||||
<Group form gap>
|
||||
<Container wrap gap>
|
||||
<InputField type="text" name="name" minLength={1} state={config} disabled={disabled} readOnly={readonly} mod="surface" required fill>
|
||||
<InputField type="text" name="name" minLength={1} state={config} disabled={disabled} readOnly={readonly} required fill>
|
||||
{translate('connections_connection_name')}
|
||||
</InputField>
|
||||
{!config.template && (
|
||||
@@ -272,7 +271,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
state={config}
|
||||
disabled={disabled}
|
||||
autoComplete={`section-${config.driverId || 'driver'} section-folder`}
|
||||
mod="surface"
|
||||
autoHide
|
||||
readOnly
|
||||
tiny
|
||||
@@ -328,6 +326,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
name="saveCredentials"
|
||||
state={config}
|
||||
disabled={disabled || readonly || config.sharedCredentials}
|
||||
mod={['primary']}
|
||||
keepSize
|
||||
>
|
||||
{translate('connections_connection_edit_save_credentials')}
|
||||
|
||||
@@ -117,7 +117,6 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
|
||||
state={handlerState.properties}
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
mod="surface"
|
||||
required
|
||||
small
|
||||
>
|
||||
@@ -129,7 +128,6 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
|
||||
state={handlerState.properties}
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
mod="surface"
|
||||
required
|
||||
tiny
|
||||
>
|
||||
@@ -143,7 +141,6 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
|
||||
state={handlerState}
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
mod="surface"
|
||||
required={handlerState.savePassword}
|
||||
tiny
|
||||
fill
|
||||
@@ -157,7 +154,6 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
|
||||
state={handlerState}
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
mod="surface"
|
||||
required={!keyAuth && handlerState.savePassword}
|
||||
description={passwordSaved ? translate('ui_processing_saved') : undefined}
|
||||
tiny
|
||||
@@ -187,7 +183,6 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
labelTooltip={aliveIntervalLabel}
|
||||
mod="surface"
|
||||
tiny
|
||||
>
|
||||
{aliveIntervalLabel}
|
||||
@@ -199,7 +194,6 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
labelTooltip={connectTimeoutLabel}
|
||||
mod="surface"
|
||||
tiny
|
||||
>
|
||||
{connectTimeoutLabel}
|
||||
|
||||
@@ -74,7 +74,6 @@ export const SettingsInfoFormField = observer<SettingsInfoFormFieldProps>(functi
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
mod="surface"
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
>
|
||||
@@ -93,7 +92,6 @@ export const SettingsInfoFormField = observer<SettingsInfoFormFieldProps>(functi
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
mod="surface"
|
||||
className={className}
|
||||
small
|
||||
onChange={onChange}
|
||||
|
||||
@@ -45,13 +45,13 @@ export const UserInfoPanel = observer<Props>(function UserInfoPanel({ user, clas
|
||||
<Group form gap>
|
||||
<GroupTitle>{translate('plugin_user_profile_info')}</GroupTitle>
|
||||
<Container wrap gap>
|
||||
<InputField type="text" name="userId" minLength={1} state={user} mod="surface" readOnly required tiny fill>
|
||||
<InputField type="text" name="userId" minLength={1} state={user} readOnly required tiny fill>
|
||||
{translate('plugin_user_profile_info_id')}
|
||||
</InputField>
|
||||
<InputField type="text" name="displayName" minLength={1} state={user} mod="surface" readOnly required tiny fill>
|
||||
<InputField type="text" name="displayName" minLength={1} state={user} readOnly required tiny fill>
|
||||
{translate('plugin_user_profile_info_displayName')}
|
||||
</InputField>
|
||||
<InputField type="text" name="authRole" mod="surface" state={user} autoHide readOnly tiny fill>
|
||||
<InputField type="text" name="authRole" state={user} autoHide readOnly tiny fill>
|
||||
{translate('authentication_user_role')}
|
||||
</InputField>
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user