Skip to content

Commit

Permalink
feat: update React components for improved type safety and compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
jikkai committed Jan 17, 2025
1 parent b643fcc commit 0bcd5f3
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 113 deletions.
4 changes: 4 additions & 0 deletions common/shared/eslint/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ exports.baseRules = {
'command/command': 'off',
'jsdoc/tag-lines': 'off',

// IMPORTANT: To ensure compatibility, some features of React 19 will be disabled.
'react/no-forward-ref': 'off',
'react/no-context-provider': 'off',

// TODO: debatable rules
'react/no-duplicate-key': 'warn',
'test/prefer-lowercase-title': 'off',
Expand Down
4 changes: 2 additions & 2 deletions packages/design/src/components/dropdown/DropdownProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ interface IDropdownProviderProps {
}

export function DropdownProvider({ visible, children, disabled = false, onVisibleChange }: IDropdownProviderProps) {
const triggerRef = useRef<HTMLDivElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLDivElement>(null!);
const overlayRef = useRef<HTMLDivElement>(null!);

const [internalShow, setInternalShow] = useState(false);
const isControlled = visible !== undefined;
Expand Down
192 changes: 88 additions & 104 deletions packages/design/src/components/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,129 +14,113 @@
* limitations under the License.
*/

import type { InputProps } from 'rc-input';
import { CloseSingle } from '@univerjs/icons';
import clsx from 'clsx';
import RcInput from 'rc-input';
import React from 'react';
import { clsx } from '../../helper/clsx';

import styles from './index.module.less';
type InputProps = React.InputHTMLAttributes<HTMLInputElement>;

export interface IInputProps extends Pick<InputProps, 'onFocus' | 'onBlur'> {
/**
* Whether the input is autoFocus
* @default false
*/
autoFocus?: boolean;

/**
* The input class name
*/
className?: string;

/**
* The input affix wrapper style
*/
affixWrapperStyle?: React.CSSProperties;

/**
* The input type
* @default text
*/
type?: 'text' | 'password';

/**
* The input placeholder
*/
placeholder?: string;

/**
* The input content value
*/
value?: string;

/**
* The input size
* @default middle
*/
size?: 'small' | 'middle' | 'large';

/**
* Whether the input is clearable
* @default false
*/
allowClear?: boolean;

/**
* Whether the input is disabled
* @default false
*/
disabled?: boolean;

/**
* Callback when user click
* @param e
*/
onClick?: (e: React.MouseEvent<HTMLInputElement>) => void;

/**
* Callback when user press a key
* @param e
*/
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;

/**
* Callback when user input
* @param value
*/
onChange?: (value: string) => void;

}

export function Input(props: IInputProps) {
const {
affixWrapperStyle,
autoFocus = false,
type = 'text',
className,
placeholder,
value,
size = 'middle',
allowClear,
disabled = false,
onClick,
onKeyDown,
onChange,
...rest
} = props;

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const { value } = e.target;
onChange?.(value);
}

const _className = clsx(className, {
[styles.inputAffixWrapperSmall]: size === 'small',
[styles.inputAffixWrapperMiddle]: size === 'middle',
[styles.inputAffixWrapperLarge]: size === 'large',
[styles.inputNotAllowClear]: !allowClear,
}, className);
export const Input = ({
autoFocus = false,
className,
affixWrapperStyle,
type = 'text',
placeholder,
value,
size = 'small',
allowClear = false,
disabled = false,
onClick,
onKeyDown,
onChange,
onFocus,
onBlur,
...props
}: IInputProps) => {
const sizeClasses = {
small: 'univer-h-8 univer-text-sm univer-px-2',
middle: 'univer-h-10 univer-text-base univer-px-3',
large: 'univer-h-12 univer-text-lg univer-px-4',
};

const handleClear = (e: React.MouseEvent) => {
e.stopPropagation();
onChange?.('');
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(e.target.value);
};

return (
<RcInput
prefixCls={styles.input}
classNames={{ affixWrapper: _className }}
styles={{ affixWrapper: affixWrapperStyle }}
autoFocus={autoFocus}
type={type}
placeholder={placeholder}
value={value}
disabled={disabled}
onClick={onClick}
onKeyDown={onKeyDown}
onChange={handleChange}
allowClear={{ clearIcon: allowClear ? <CloseSingle /> : <></> }}
{...rest}
/>
<div
className={clsx(
'univer-relative univer-inline-flex univer-w-full univer-items-center univer-rounded-md',
disabled && 'univer-cursor-not-allowed univer-opacity-50',
className
)}
style={affixWrapperStyle}
>
<input
type={type}
className={clsx(
`
univer-box-border univer-w-full univer-rounded-md univer-border univer-border-solid
univer-border-gray-300 univer-bg-white
`,
'univer-transition-colors univer-duration-200',
'placeholder:univer-text-gray-400',
`
focus:univer-border-blue-500 focus:univer-outline-none focus:univer-ring-2
focus:univer-ring-blue-500/20
`,
disabled && 'univer-cursor-not-allowed univer-bg-gray-50',
allowClear && 'univer-pr-8',
sizeClasses[size]
)}
placeholder={placeholder}
value={value}
disabled={disabled}
autoFocus={autoFocus}
onClick={onClick}
onKeyDown={onKeyDown}
onChange={handleChange}
onFocus={onFocus}
onBlur={onBlur}
{...props}
/>
{allowClear && value && !disabled && (
<button
type="button"
onClick={handleClear}
className={clsx(
'univer-absolute univer-right-2 univer-rounded-full univer-p-1',
`
univer-text-gray-400
hover:univer-text-gray-500
`,
'univer-transition-colors univer-duration-200',
'focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-blue-500/20'
)}
>
<CloseSingle className="univer-h-4 univer-w-4" />
</button>
)}
</div>
);
}
};
2 changes: 1 addition & 1 deletion packages/design/src/components/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export function Tooltip({ visible, asChild = false, title, children, placement =
React.Children.only(children) as React.ReactElement,
{
ref: triggerRef,
}
} as any
);

return (
Expand Down
2 changes: 1 addition & 1 deletion packages/docs-ui/src/views/rich-text-editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const RichTextEditor = forwardRef<Editor, IRichTextEditorProps>((props, r
const onFocusChange = useEvent(_onFocusChange);
const onClickOutside = useEvent(_onClickOutside);
const [height, setHeight] = useState(defaultHeight);
const formulaEditorContainerRef = React.useRef<HTMLDivElement>(null);
const formulaEditorContainerRef = React.useRef<HTMLDivElement>(null!);
const editorId = useMemo(() => propsEditorId ?? createInternalEditorID(`RICH_TEXT_EDITOR-${generateRandomId(4)}`), [propsEditorId]);
const editor = useEditor({
editorId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { FormulaEditor } from '@univerjs/sheets-formula-ui';
import { ILayoutService, useScrollYOverContainer, useSidebarClick } from '@univerjs/ui';

import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';

import stylesBase from '../index.module.less';
import styles from './index.module.less';
Expand Down Expand Up @@ -108,7 +108,7 @@ interface IconGroupListProps {
onClick: (iconType: IIconType) => void;
iconType?: IIconType;
};
const IconGroupList = ({ ref, ...props }: IconGroupListProps & { ref: React.RefObject<HTMLDivElement | null> }) => {
const IconGroupList = forwardRef<HTMLDivElement, IconGroupListProps>((props, ref) => {
const localeService = useDependency(LocaleService);

const handleClick = (iconType: IIconType) => {
Expand Down Expand Up @@ -137,7 +137,7 @@ const IconGroupList = ({ ref, ...props }: IconGroupListProps & { ref: React.RefO
})}
</div>
);
};
});

const IconItemList = (props: { onClick: (iconType: IIconType, iconId: string) => void; iconType?: IIconType; iconId: string }) => {
const list = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function FormulaEditor(props: IFormulaEditorProps) {

const onFormulaSelectingChange = useEvent(propOnFormulaSelectingChange);
const searchFunctionRef = useRef<HTMLElement>(null);
const editorRef = useRef<Editor>();
const editorRef = useRef<Editor>(undefined);
const editor = editorRef.current;
const [isFocus, isFocusSet] = useState(_isFocus);
const formulaEditorContainerRef = useRef(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function RangeSelector(props: IRangeSelectorProps) {
const [rangeDialogVisible, rangeDialogVisibleSet] = useState(false);
const [isFocus, isFocusSet] = useState(_isFocus);
const editorId = useMemo(() => createInternalEditorID(`${RANGE_SELECTOR_SYMBOLS}-${generateRandomId(4)}`), []);
const editorRef = useRef<Editor>();
const editorRef = useRef<Editor>(undefined);
const editor = editorRef.current;
const containerRef = useRef<HTMLDivElement>(null);
const univerInstanceService = useDependency(IUniverInstanceService);
Expand Down

0 comments on commit 0bcd5f3

Please sign in to comment.