feat(design): input support slot usage (#5095)

This commit is contained in:
Wenzhao Hu
2025-04-27 10:41:12 +08:00
committed by GitHub
parent fa53a11658
commit 3195ec60a9
6 changed files with 98 additions and 41 deletions
@@ -67,7 +67,6 @@
},
"dependencies": {
"@univerjs/core": "workspace:*",
"@univerjs/design": "workspace:*",
"@univerjs/docs": "workspace:*",
"@univerjs/docs-ui": "workspace:*",
"@univerjs/engine-render": "workspace:*",
@@ -16,6 +16,7 @@
import type { Meta } from '@storybook/react';
import { Pager } from '../pager/Pager';
import { Input } from './Input';
const meta: Meta<typeof Input> = {
@@ -62,3 +63,24 @@ export const InputDisabled = {
);
},
};
export const Clear = {
render() {
return (
<>
<Input allowClear />
<Input value="content" allowClear />
</>
);
},
};
export const ClearWithPage = {
render() {
return (
<>
<Input value="with some very long content to push content offset" allowClear slot={<Pager value={1} total={10} />} />
</>
);
},
};
+53 -16
View File
@@ -17,7 +17,7 @@
import type { VariantProps } from 'class-variance-authority';
import { CloseSingle } from '@univerjs/icons';
import { cva } from 'class-variance-authority';
import { forwardRef } from 'react';
import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
import { clsx } from '../../helper/clsx';
type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
@@ -26,7 +26,7 @@ export const inputVariants = cva(
`
univer-box-border univer-w-full univer-rounded-md univer-border univer-border-solid univer-border-gray-200
univer-bg-white univer-transition-colors univer-duration-200
dark:univer-bg-gray-700 dark:univer-border-gray-600 dark:univer-text-white
dark:univer-border-gray-600 dark:univer-bg-gray-700 dark:univer-text-white
focus:univer-border-primary-500 focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-500/20
placeholder:univer-text-gray-400
`,
@@ -59,6 +59,7 @@ export interface IInputProps extends Pick<InputProps, 'onFocus' | 'onBlur'>,
onChange?: (value: string) => void;
inputClass?: string;
inputStyle?: React.CSSProperties;
slot?: React.ReactNode;
}
export const Input = forwardRef<HTMLInputElement, IInputProps>(
@@ -77,6 +78,7 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
onChange,
onFocus,
onBlur,
slot,
inputClass,
inputStyle,
...props
@@ -90,6 +92,30 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
onChange?.(e.target.value);
};
const hasSlotContent = useMemo(() => {
return (allowClear && value && !disabled) || slot;
}, [allowClear, disabled, slot, value]);
const [paddingRight, setPaddingRight] = useState(0);
const slotRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let observer: MutationObserver | null = null;
if (slot && slotRef.current) {
observer = new MutationObserver(() => {
if (slotRef.current) {
setPaddingRight(slotRef.current.offsetWidth + 4 * 2);
}
});
observer.observe(slotRef.current, { childList: true, subtree: true });
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setPaddingRight(slotRef.current.offsetWidth + 4 * 2);
}
return () => observer?.disconnect();
}, [slotRef.current]);
return (
<div
className={clsx(
@@ -105,10 +131,10 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
className={clsx(
inputVariants({ size }),
disabled && `
univer-cursor-not-allowed univer-text-gray-400 univer-bg-gray-50
univer-cursor-not-allowed univer-bg-gray-50 univer-text-gray-400
dark:univer-text-gray-500
`,
allowClear && 'univer-pr-8',
(allowClear && !slot) && 'univer-pr-8',
inputClass
)}
placeholder={placeholder}
@@ -120,23 +146,34 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
onChange={handleChange}
onFocus={onFocus}
onBlur={onBlur}
style={inputStyle}
style={{ ...inputStyle, paddingRight }}
{...props}
/>
{allowClear && value && !disabled && (
<button
type="button"
onClick={handleClear}
{hasSlotContent && (
<div
className={`
univer-absolute univer-right-2 univer-flex univer-items-center univer-rounded-full
univer-border-none univer-bg-transparent univer-p-1 univer-text-gray-400
univer-transition-colors univer-duration-200
focus:univer-outline-none
hover:univer-text-gray-500
univer-absolute univer-right-2 univer-flex univer-items-center univer-gap-1
univer-rounded-full
`}
ref={slotRef}
>
<CloseSingle className="univer-size-4" />
</button>
{slot}
{allowClear && value && !disabled && (
<button
type="button"
onClick={handleClear}
className={`
univer-flex univer-size-4 univer-cursor-pointer univer-rounded-full univer-border-none
univer-bg-transparent univer-p-1 univer-text-gray-400 univer-transition-colors
univer-duration-200
focus:univer-outline-none
hover:univer-text-gray-500
`}
>
<CloseSingle className="univer-size-3" />
</button>
)}
</div>
)}
</div>
);
@@ -38,7 +38,7 @@ function useFindInputFocus(findReplaceService: IFindReplaceService, ref: Forward
}, []);
const selectHasFocus = useCallback(() => {
const allInputs = document.querySelectorAll('.univer-find-replace-dialog-container .univer-select-selection-search-input');
const allInputs = document.querySelectorAll('[data-u-comp="find-replace-dialog"] [data-u-comp="search-input"]');
return Array.from(allInputs).some((input) => input === document.activeElement);
}, []);
@@ -95,7 +95,7 @@ export const FindDialog = forwardRef(function FindDialogImpl(_props, ref) {
);
});
export const ReplaceDialog = forwardRef(function ReplaceDIalogImpl(_props, ref) {
export const ReplaceDialog = forwardRef(function ReplaceDialogImpl(_props, ref) {
const findReplaceService = useDependency(IFindReplaceService);
const localeService = useDependency(LocaleService);
const commandService = useDependency(ICommandService);
@@ -297,7 +297,7 @@ export function FindReplaceDialog() {
}, [setDialogContainerFocus, setDialogInputFocus]);
return (
<div ref={dialogContainerRef}>
<div ref={dialogContainerRef} data-u-comp="find-replace-dialog">
{!state.replaceRevealed ? <FindDialog ref={focusRef} /> : <ReplaceDialog ref={focusRef} />}
</div>
);
@@ -40,30 +40,32 @@ export function SearchInput(props: ISearchInputProps) {
return (
<div className="univer-relative univer-flex univer-items-center univer-gap-2" onDrag={(e) => e.stopPropagation()}>
<Input
data-u-comp="search-input"
autoFocus
placeholder={localeService.t('find-replace.dialog.find-placeholder')}
value={findString}
onChange={(value) => onChange?.(value)}
slot={(
<Pager
loop
text={text}
value={matchesPosition}
total={matchesCount}
onChange={(newIndex) => {
if (matchesPosition === matchesCount && newIndex === 1) {
findReplaceService.moveToNextMatch();
} else if (matchesPosition === 1 && newIndex === matchesCount) {
findReplaceService.moveToPreviousMatch();
} else if (newIndex < matchesPosition) {
findReplaceService.moveToPreviousMatch();
} else {
findReplaceService.moveToNextMatch();
}
}}
/>
)}
{...rest}
/>
<Pager
loop
text={text}
value={matchesPosition}
total={matchesCount}
onChange={(newIndex) => {
if (matchesPosition === matchesCount && newIndex === 1) {
findReplaceService.moveToNextMatch();
} else if (matchesPosition === 1 && newIndex === matchesCount) {
findReplaceService.moveToPreviousMatch();
} else if (newIndex < matchesPosition) {
findReplaceService.moveToPreviousMatch();
} else {
findReplaceService.moveToNextMatch();
}
}}
/>
</div>
);
}
-3
View File
@@ -804,9 +804,6 @@ importers:
'@univerjs/core':
specifier: workspace:*
version: link:../../packages/core
'@univerjs/design':
specifier: workspace:*
version: link:../../packages/design
'@univerjs/docs':
specifier: workspace:*
version: link:../../packages/docs