mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-25 09:34:18 +08:00
dbeaver/pro#5394 feat: null and boolean formatter for base grid (#3359)
* dbeaver/pro#5394 feat: null and boolean formatter for base grid * feat: reuse base grid formatters * chore: try to adjust workflow concurrency * chore: trigger workflow * chore: trigger workflow cancel * chore: fix label styles --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"sideEffects": [],
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./*.css": "./src/*.css",
|
||||
"./*": "./lib/*.js"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -32,6 +33,7 @@
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dbeaver/ui-kit": "workspace:^",
|
||||
"react": "^19",
|
||||
"react-data-grid": "file:./artifacts",
|
||||
"react-dom": "^19"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import url('@dbeaver/ui-kit/index.css');
|
||||
@import url('react-data-grid/lib/styles.css') layer(components);
|
||||
|
||||
@layer components.rdg.MeasuringCell {
|
||||
@@ -5,3 +6,24 @@
|
||||
max-width: var(--rdg-max-auto-size, 350px);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components.rdg.Cell {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.rdg-cell-frozen {
|
||||
&:nth-last-child(1 of &) {
|
||||
box-shadow: none;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NullFormatter } from './NullFormatter.js';
|
||||
import { Checkbox } from '@dbeaver/ui-kit';
|
||||
|
||||
interface Props {
|
||||
value: boolean | null;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function BooleanFormatter({ value, className, onClick }: Props) {
|
||||
if (value === null) {
|
||||
return <NullFormatter className={className} onClick={onClick} />;
|
||||
}
|
||||
return <Checkbox size="small" checked={value} className={className} onClick={onClick} />;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NullFormatter } from './NullFormatter.js';
|
||||
|
||||
interface Props {
|
||||
value: Date | null;
|
||||
type?: 'time' | 'date' | 'datetime';
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function DateFormatter({ value, type = 'datetime', onClick }: Props) {
|
||||
if (value === null) {
|
||||
return <NullFormatter />;
|
||||
}
|
||||
return (
|
||||
<div onClick={onClick}>
|
||||
{value.toLocaleString(undefined, {
|
||||
dateStyle: type === 'date' ? 'long' : undefined,
|
||||
timeStyle: type === 'time' ? 'long' : undefined,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.nullValue {
|
||||
text-transform: uppercase;
|
||||
opacity: 0.65;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import styles from './NullFormatter.module.css';
|
||||
import { clsx } from '@dbeaver/ui-kit';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function NullFormatter({ className, onClick }: Props) {
|
||||
return (
|
||||
<span className={clsx(styles['nullValue'], className)} onClick={onClick}>
|
||||
[NULL]
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NullFormatter } from './NullFormatter.js';
|
||||
|
||||
interface Props {
|
||||
value: number | null;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function NumberFormatter({ value, className, onClick }: Props) {
|
||||
if (value === null) {
|
||||
return <NullFormatter className={className} onClick={onClick} />;
|
||||
}
|
||||
return (
|
||||
<div style={{ textAlign: 'right' }} className={className} onClick={onClick}>
|
||||
{value.toLocaleString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,3 +4,8 @@ export * from './DataGridCellInnerContext.js';
|
||||
export * from './IGridReactiveValue.js';
|
||||
export * from './useCreateGridReactiveValue.js';
|
||||
export * from './useGridReactiveValue.js';
|
||||
|
||||
export * from './formatters/NullFormatter.js';
|
||||
export * from './formatters/BooleanFormatter.js';
|
||||
export * from './formatters/NumberFormatter.js';
|
||||
export * from './formatters/DateFormatter.js';
|
||||
|
||||
@@ -12,5 +12,10 @@
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../ui-kit"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
".": "./lib/index.js",
|
||||
"./*.css": "./src/*.css",
|
||||
"./*": "./lib/*.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Button as AriaButton, type ButtonProps as AriaKitButtonProps } from '@a
|
||||
import clsx from 'clsx';
|
||||
import './Button.css';
|
||||
import type { ControlSize } from '../types/controls.js';
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
|
||||
export interface ButtonProps extends Omit<AriaKitButtonProps, 'clickOnEnter' | 'clickOnSpace'> {
|
||||
variant?: 'primary' | 'secondary' | 'danger';
|
||||
@@ -17,7 +18,7 @@ export interface ButtonProps extends Omit<AriaKitButtonProps, 'clickOnEnter' | '
|
||||
loader?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Button({ className, variant = 'primary', size = 'medium', loading, loader, children, onClick, ...props }: ButtonProps) {
|
||||
export function ButtonBase({ className, variant = 'primary', size = 'medium', loading, loader, children, onClick, ...props }: ButtonProps) {
|
||||
const classToApply = clsx('dbv-kit-button', `dbv-kit-button--${variant}`, `dbv-kit-button--${size}`, className);
|
||||
|
||||
if (loading) {
|
||||
@@ -41,7 +42,9 @@ export interface ButtonIconProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
*
|
||||
* @param props.placement This property is needed to adjust icon placement inside a button. The icon with placement="start" will cut the inline-start padding. placement="end" will affect the padding-inline-end accordingly. This property supports RTL and LTR, so you don't need to think about it.
|
||||
*/
|
||||
Button.Icon = function ButtonIcon({ className, children, placement }: ButtonIconProps) {
|
||||
ButtonBase.Icon = function ButtonBaseIcon({ className, children, placement }: ButtonIconProps) {
|
||||
const classToApply = clsx('dbv-kit-button__icon', placement && `dbv-kit-button__icon--${placement}`, className);
|
||||
return <span className={classToApply}>{children}</span>;
|
||||
};
|
||||
|
||||
export const Button = componentProviderWrapper('Button', ButtonBase);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import './Checkbox.css';
|
||||
import type { ControlSize } from '../types/controls.js';
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
|
||||
export interface CheckboxProps extends Omit<AriaKitCheckboxProps, 'render' | 'size'> {
|
||||
size?: ControlSize;
|
||||
@@ -19,7 +20,7 @@ export interface CheckboxProps extends Omit<AriaKitCheckboxProps, 'render' | 'si
|
||||
indeterminateIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Checkbox({ children, className, icon, indeterminate, indeterminateIcon, size = 'medium', ...props }: CheckboxProps) {
|
||||
export function CheckboxBase({ children, className, icon, indeterminate, indeterminateIcon, size = 'medium', ...props }: CheckboxProps) {
|
||||
const [innerChecked, setInnerChecked] = useState(props.defaultChecked ?? false);
|
||||
const checked = props.checked ?? innerChecked;
|
||||
|
||||
@@ -70,3 +71,5 @@ export function Checkbox({ children, className, icon, indeterminate, indetermina
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export const Checkbox = componentProviderWrapper('Checkbox', CheckboxBase);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { createContext } from 'react';
|
||||
import type { ButtonBase } from './Button/Button.js';
|
||||
import type { CheckboxBase } from './Checkbox/Checkbox.js';
|
||||
import type { Radio } from './Radio/Radio.js';
|
||||
import type { InputBase } from './Input/Input.js';
|
||||
import type { RadioGroup } from './Radio/RadioGroup.js';
|
||||
import type { Select } from './Select/Select.js';
|
||||
import type { SelectField } from './Select/SelectField.js';
|
||||
|
||||
export interface IComponentProvider {
|
||||
Button?: typeof ButtonBase;
|
||||
Input?: typeof InputBase;
|
||||
Checkbox?: typeof CheckboxBase;
|
||||
Radio?: typeof Radio;
|
||||
RadioGroup?: typeof RadioGroup;
|
||||
Select?: typeof Select;
|
||||
SelectField?: typeof SelectField;
|
||||
}
|
||||
export const ComponentProvider = createContext<IComponentProvider>({});
|
||||
@@ -9,13 +9,14 @@
|
||||
import { type ComponentPropsWithRef } from 'react';
|
||||
import './Input.css';
|
||||
import clsx from 'clsx';
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
|
||||
export interface InputProps extends Omit<ComponentPropsWithRef<'input'>, 'size'> {
|
||||
size?: 'small' | 'medium' | 'large' | 'xlarge';
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Input: React.FC<InputProps> = function Input({ size, className, ...props }) {
|
||||
export const InputBase: React.FC<InputProps> = function Input({ size, className, ...props }) {
|
||||
const classNameToApply = clsx(`dbv-kit-input`, `dbv-kit-input--${size ?? 'medium'}`, className);
|
||||
return (
|
||||
<label className="dbv-kit-input-wrapper">
|
||||
@@ -24,3 +25,5 @@ export const Input: React.FC<InputProps> = function Input({ size, className, ...
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export const Input = componentProviderWrapper('Input', InputBase);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { use } from 'react';
|
||||
import { ComponentProvider, type IComponentProvider } from './ComponentProvider.js';
|
||||
|
||||
export function componentProviderWrapper<T extends keyof IComponentProvider>(
|
||||
key: T,
|
||||
component: Required<IComponentProvider>[T],
|
||||
): Required<IComponentProvider>[T] {
|
||||
return function componentProviderWrapperInner(props: Required<IComponentProvider>[T] extends React.FC<infer P> ? P : never) {
|
||||
const provider = use(ComponentProvider);
|
||||
const Component = provider[key] || component;
|
||||
return <Component {...(props as any)} />;
|
||||
} as Required<IComponentProvider>[T];
|
||||
}
|
||||
@@ -18,9 +18,9 @@ export {
|
||||
type FocusTrapRegionProps,
|
||||
} from '@ariakit/react';
|
||||
|
||||
export { Button, type ButtonProps } from './Button/Button.js';
|
||||
export { Checkbox, type CheckboxProps } from './Checkbox/Checkbox.js';
|
||||
export { Input, type InputProps } from './Input/Input.js';
|
||||
export { Button, ButtonBase, type ButtonProps } from './Button/Button.js';
|
||||
export { Checkbox, CheckboxBase, type CheckboxProps } from './Checkbox/Checkbox.js';
|
||||
export { Input, InputBase, type InputProps } from './Input/Input.js';
|
||||
export {
|
||||
Select,
|
||||
useSelectContext,
|
||||
@@ -34,3 +34,4 @@ export {
|
||||
export { SelectField, type SelectFieldProps, type SelectItem } from './Select/SelectField.js';
|
||||
export { Radio, RadioGroup, useRadioContext, useRadioStore, type RadioProviderProps, type RadioProps, type RadioGroupProps } from './Radio/index.js';
|
||||
export * from './utils/clsx.js';
|
||||
export * from './ComponentProvider.js';
|
||||
|
||||
@@ -341,6 +341,7 @@ __metadata:
|
||||
resolution: "@dbeaver/react-data-grid@workspace:@dbeaver/react-data-grid"
|
||||
dependencies:
|
||||
"@dbeaver/tsconfig": "workspace:^"
|
||||
"@dbeaver/ui-kit": "workspace:^"
|
||||
"@types/react": "npm:^19"
|
||||
"@types/react-dom": "npm:^19"
|
||||
react: "npm:^19"
|
||||
@@ -361,7 +362,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@dbeaver/ui-kit@workspace:@dbeaver/ui-kit":
|
||||
"@dbeaver/ui-kit@workspace:@dbeaver/ui-kit, @dbeaver/ui-kit@workspace:^":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@dbeaver/ui-kit@workspace:@dbeaver/ui-kit"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user