GrafanaUI: Size AutoSizeInput correctly when used with suffix/prefix (#96575)

* GrafanaUI: Size AutoSizeInput correctly when used with suffix/prefix

* comment

* reword comment
This commit is contained in:
Josh Hunt 2024-11-19 14:53:45 +00:00 committed by GitHub
parent efb420dd78
commit 4cc28c76fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 68 additions and 36 deletions

View File

@ -3,6 +3,7 @@ import * as React from 'react';
import { measureText } from '../../utils/measureText';
import { AutoSizeInputContext } from './AutoSizeInputContext';
import { Input, Props as InputProps } from './Input';
export interface Props extends InputProps {
@ -44,34 +45,38 @@ export const AutoSizeInput = React.forwardRef<HTMLInputElement, Props>((props, r
}, [placeholder, value, minWidth, maxWidth]);
return (
<Input
{...restProps}
placeholder={placeholder}
ref={ref}
value={value.toString()}
onChange={(event) => {
if (onChange) {
onChange(event);
}
setValue(event.currentTarget.value);
}}
width={inputWidth}
onBlur={(event) => {
if (onBlur) {
onBlur(event);
} else if (onCommitChange) {
onCommitChange(event);
}
}}
onKeyDown={(event) => {
if (onKeyDown) {
onKeyDown(event);
} else if (event.key === 'Enter' && onCommitChange) {
onCommitChange(event);
}
}}
data-testid={'autosize-input'}
/>
// Used to tell Input to increase the width properly of the input to fit the text.
// See comment in Input.tsx for more details
<AutoSizeInputContext.Provider value={true}>
<Input
{...restProps}
placeholder={placeholder}
ref={ref}
value={value.toString()}
onChange={(event) => {
if (onChange) {
onChange(event);
}
setValue(event.currentTarget.value);
}}
onBlur={(event) => {
if (onBlur) {
onBlur(event);
} else if (onCommitChange) {
onCommitChange(event);
}
}}
onKeyDown={(event) => {
if (onKeyDown) {
onKeyDown(event);
} else if (event.key === 'Enter' && onCommitChange) {
onCommitChange(event);
}
}}
width={inputWidth}
data-testid="autosize-input"
/>
</AutoSizeInputContext.Provider>
);
});

View File

@ -0,0 +1,6 @@
import React from 'react';
// Used to tell Input to increase the width properly of the input to fit the text.
// See comment in Input.tsx for more details
export const AutoSizeInputContext = React.createContext(false);
AutoSizeInputContext.displayName = 'AutoSizeInputContext';

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import { forwardRef, HTMLProps, ReactNode } from 'react';
import { forwardRef, HTMLProps, ReactNode, useContext } from 'react';
import useMeasure from 'react-use/lib/useMeasure';
import { GrafanaTheme2 } from '@grafana/data';
@ -8,6 +8,8 @@ import { stylesFactory, useTheme2 } from '../../themes';
import { getFocusStyle, sharedInputStyle } from '../Forms/commonStyles';
import { Spinner } from '../Spinner/Spinner';
import { AutoSizeInputContext } from './AutoSizeInputContext';
export interface Props extends Omit<HTMLProps<HTMLInputElement>, 'prefix' | 'size'> {
/** Sets the width to a multiple of 8px. Should only be used with inline forms. Setting width of the container is preferred in other cases.*/
width?: number;
@ -32,7 +34,17 @@ interface StyleDeps {
}
export const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
const { className, addonAfter, addonBefore, prefix, suffix, invalid, loading, width = 0, ...restProps } = props;
const {
className,
addonAfter,
addonBefore,
prefix,
suffix: suffixProp,
invalid,
loading,
width = 0,
...restProps
} = props;
/**
* Prefix & suffix are positioned absolutely within inputWrapper. We use client rects below to apply correct padding to the input
* when prefix/suffix is larger than default (28px = 16px(icon) + 12px(left/right paddings)).
@ -41,13 +53,24 @@ export const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
const [prefixRef, prefixRect] = useMeasure<HTMLDivElement>();
const [suffixRef, suffixRect] = useMeasure<HTMLDivElement>();
// Yes, this is gross - When Input is being wrapped by AutoSizeInput, add the suffix/prefix width to the overall width
// so the text content is not clipped. The intention is to make all the input's text appear without overflow/clipping,
// which isn't normally how width is used in this component.
// This behaviour is not controlled via a prop so we can limit API surface, and remove this as a 'breaking change' later
// if a better solution is found.
const isInAutoSizeInput = useContext(AutoSizeInputContext);
const accessoriesWidth = (prefixRect.width || 0) + (suffixRect.width || 0);
const finalWidth = isInAutoSizeInput && width ? width + accessoriesWidth / 8 : width;
const theme = useTheme2();
const styles = getInputStyles({ theme, invalid: !!invalid, width });
const styles = getInputStyles({ theme, invalid: !!invalid, width: finalWidth });
const suffix = suffixProp || (loading && <Spinner inline={true} />);
return (
<div className={cx(styles.wrapper, className)} data-testid={'input-wrapper'}>
<div className={cx(styles.wrapper, className)} data-testid="input-wrapper">
{!!addonBefore && <div className={styles.addon}>{addonBefore}</div>}
<div className={styles.inputWrapper}>
{prefix && (
<div className={styles.prefix} ref={prefixRef}>
@ -65,14 +88,12 @@ export const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
}}
/>
{(suffix || loading) && (
{suffix && (
<div className={styles.suffix} ref={suffixRef}>
{loading && <Spinner className={styles.loadingIndicator} inline={true} />}
{suffix}
</div>
)}
</div>
{!!addonAfter && <div className={styles.addon}>{addonAfter}</div>}
</div>
);