Skip to content

Commit

Permalink
Simplify the Input element type
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer committed Aug 14, 2024
1 parent f583d05 commit 8c509d1
Show file tree
Hide file tree
Showing 18 changed files with 74 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { describe, expect, it } from 'vitest';
import { createRef, useState, type ChangeEvent } from 'react';

import { render, userEvent, axe, screen } from '../../util/test-utils.js';
import type { InputElement } from '../Input/index.js';

import { CurrencyInput, type CurrencyInputProps } from './CurrencyInput.js';

Expand All @@ -30,7 +29,7 @@ const defaultProps = {

describe('CurrencyInput', () => {
it('should forward a ref', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
render(<CurrencyInput {...defaultProps} ref={ref} />);
const input: HTMLInputElement = screen.getByRole('textbox');
expect(ref.current).toBe(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { resolveCurrencyFormat } from '@sumup-oss/intl';
import { NumericFormat, type NumericFormatProps } from 'react-number-format';

import { clsx } from '../../styles/clsx.js';
import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { Input, type InputProps } from '../Input/index.js';

import { formatPlaceholder } from './CurrencyInputService.js';
import classes from './CurrencyInput.module.css';
Expand Down Expand Up @@ -73,7 +73,7 @@ const DUMMY_DELIMITER = '?';
* the symbol according to the locale. The corresponding service exports a
* parser for formatting values automatically.
*/
export const CurrencyInput = forwardRef<InputElement, CurrencyInputProps>(
export const CurrencyInput = forwardRef<HTMLInputElement, CurrencyInputProps>(
(
{
locale,
Expand Down
3 changes: 1 addition & 2 deletions packages/circuit-ui/components/DateInput/DateInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ import { describe, expect, it } from 'vitest';
import { createRef } from 'react';

import { render, axe } from '../../util/test-utils.js';
import type { InputElement } from '../Input/index.js';

import { DateInput } from './DateInput.js';

describe('DateInput', () => {
const baseProps = { label: 'Date' };

it('should forward a ref', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
const { container } = render(<DateInput {...baseProps} ref={ref} />);
const input = container.querySelector('input');
expect(ref.current).toBe(input);
Expand Down
4 changes: 2 additions & 2 deletions packages/circuit-ui/components/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { forwardRef, useState, useEffect } from 'react';
import { PatternFormat } from 'react-number-format';

import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { Input, type InputProps } from '../Input/index.js';
import { clsx } from '../../styles/clsx.js';

import classes from './DateInput.module.css';
Expand All @@ -42,7 +42,7 @@ export interface DateInputProps
* DateInput component for forms.
* The input value is always a string in the format `YYYY-MM-DD`.
*/
export const DateInput = forwardRef<InputElement, DateInputProps>(
export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
({ inputClassName, ...props }, ref) => {
// When server-side rendering, we assume that the user's browser supports
// the native date input.
Expand Down
6 changes: 3 additions & 3 deletions packages/circuit-ui/components/Input/Input.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { createRef } from 'react';

import { render, axe, screen } from '../../util/test-utils.js';

import { Input, type InputElement } from './Input.js';
import { Input } from './Input.js';

const defaultProps = {
label: 'Label',
Expand All @@ -35,14 +35,14 @@ describe('Input', () => {
});

it('should forward a ref to the input', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
const { container } = render(<Input ref={ref} {...defaultProps} />);
const input = container.querySelector('input');
expect(ref.current).toBe(input);
});

it('should forward a ref to the textarea', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
const { container } = render(
<Input as="textarea" ref={ref} {...defaultProps} />,
);
Expand Down
32 changes: 22 additions & 10 deletions packages/circuit-ui/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
useId,
type ComponentType,
type InputHTMLAttributes,
type TextareaHTMLAttributes,
} from 'react';

import {
Expand All @@ -38,19 +37,18 @@ import { clsx } from '../../styles/clsx.js';

import classes from './Input.module.css';

export type InputElement = HTMLInputElement & HTMLTextAreaElement;
type CircuitInputHTMLAttributes = InputHTMLAttributes<HTMLInputElement> &
TextareaHTMLAttributes<HTMLTextAreaElement>;
/**
* @deprecated
*
* Use the `HTMLInputElement` or `HTMLTextAreaElement` interfaces instead.
*/
export type InputElement = HTMLInputElement;

export interface InputProps extends CircuitInputHTMLAttributes {
export interface BaseInputProps {
/**
* A clear and concise description of the input purpose.
*/
label: string;
/**
* The HTML input element to render.
*/
as?: 'input' | 'textarea';
/**
* A unique identifier for the input field. If not defined, a randomly
* generated id is used.
Expand Down Expand Up @@ -106,10 +104,21 @@ export interface InputProps extends CircuitInputHTMLAttributes {
inputClassName?: string;
}

export interface InputProps
extends BaseInputProps,
InputHTMLAttributes<HTMLInputElement> {
/**
* @private
*
* Use the {@link TextArea} component.
*/
as?: 'input' | 'textarea';
}

/**
* Input component for forms. Takes optional prefix and suffix as render props.
*/
export const Input = forwardRef<InputElement, InputProps>(
export const Input = forwardRef<HTMLInputElement, InputProps>(
(
{
value,
Expand Down Expand Up @@ -175,6 +184,9 @@ export const Input = forwardRef<InputElement, InputProps>(
<Element
id={inputId}
value={value}
// @ts-expect-error The Input component renders as an `input` element
// by default. The types are overwritten as necessary in the
// TextArea component.
ref={ref}
aria-describedby={descriptionIds}
className={clsx(
Expand Down
2 changes: 1 addition & 1 deletion packages/circuit-ui/components/Input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@

export { Input } from './Input.js';

export type { InputProps, InputElement } from './Input.js';
export type { InputProps, BaseInputProps, InputElement } from './Input.js';
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { describe, expect, it } from 'vitest';
import { createRef, useState, type ChangeEvent } from 'react';

import { render, userEvent, axe, screen } from '../../util/test-utils.js';
import type { InputElement } from '../Input/index.js';

import {
PercentageInput,
Expand All @@ -31,7 +30,7 @@ const defaultProps = {

describe('PercentageInput', () => {
it('should forward a ref', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
render(<PercentageInput {...defaultProps} ref={ref} />);
const input = screen.getByRole('textbox');
expect(ref.current).toBe(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { resolveNumberFormat } from '@sumup-oss/intl';
import { NumericFormat, type NumericFormatProps } from 'react-number-format';

import { clsx } from '../../styles/clsx.js';
import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { Input, type InputProps } from '../Input/index.js';

import { formatPlaceholder } from './PercentageInputService.js';
import classes from './PercentageInput.module.css';
Expand Down Expand Up @@ -62,7 +62,10 @@ const DEFAULT_FORMAT = {
/**
* PercentageInput component for fractional values
*/
export const PercentageInput = forwardRef<InputElement, PercentageInputProps>(
export const PercentageInput = forwardRef<
HTMLInputElement,
PercentageInputProps
>(
(
{
locale,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { describe, it, vi, expect } from 'vitest';
import { createRef } from 'react';

import { axe, render, screen, userEvent } from '../../util/test-utils.js';
import type { InputElement } from '../Input/Input.js';

import {
PhoneNumberInput,
Expand Down Expand Up @@ -67,7 +66,7 @@ describe('PhoneNumberInput', () => {
});

it('should forward a ref to the subscriber number input', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
const props = {
...defaultProps,
subscriberNumber: { ...defaultProps.subscriberNumber, ref },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from 'react';

import { Select, type SelectProps } from '../Select/index.js';
import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { Input, type InputProps } from '../Input/index.js';
import {
FieldLabelText,
FieldLegend,
Expand Down Expand Up @@ -144,7 +144,7 @@ export interface PhoneNumberInputProps
/**
* The ref to the country code selector HTML DOM element.
*/
ref?: ForwardedRef<HTMLSelectElement | InputElement>;
ref?: ForwardedRef<HTMLSelectElement | HTMLInputElement>;
/**
* Render prop that should render a left-aligned overlay icon or element.
* Receives a className prop.
Expand Down Expand Up @@ -185,7 +185,7 @@ export interface PhoneNumberInputProps
/**
* The ref to the subscriber number input HTML DOM element.
*/
ref?: ForwardedRef<InputElement>;
ref?: ForwardedRef<HTMLInputElement>;
};
}

Expand Down Expand Up @@ -218,8 +218,8 @@ export const PhoneNumberInput = forwardRef<
},
ref,
) => {
const countryCodeRef = useRef<HTMLSelectElement | InputElement>(null);
const subscriberNumberRef = useRef<InputElement>(null);
const countryCodeRef = useRef<HTMLSelectElement | HTMLInputElement>(null);
const subscriberNumberRef = useRef<HTMLInputElement>(null);

const validationHintId = useId();

Expand Down Expand Up @@ -361,8 +361,8 @@ export const PhoneNumberInput = forwardRef<
readOnly={true}
onChange={() => {}}
ref={applyMultipleRefs(
countryCodeRef as RefObject<InputElement>,
countryCode.ref as ForwardedRef<InputElement>,
countryCodeRef as RefObject<HTMLInputElement>,
countryCode.ref as ForwardedRef<HTMLInputElement>,
)}
renderPrefix={countryCode.renderPrefix}
/>
Expand Down Expand Up @@ -401,7 +401,7 @@ export const PhoneNumberInput = forwardRef<
{...subscriberNumber}
invalid={invalid || subscriberNumber.invalid}
readOnly={readOnly || subscriberNumber.readonly}
onChange={eachFn<[ChangeEvent<InputElement>]>([
onChange={eachFn<[ChangeEvent<HTMLInputElement>]>([
subscriberNumber.onChange,
handleChange,
])}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { describe, expect, it, vi } from 'vitest';
import { createRef } from 'react';

import { render, axe, screen } from '../../util/test-utils.js';
import type { InputElement } from '../Input/index.js';

import { SearchInput } from './SearchInput.js';

Expand Down Expand Up @@ -45,7 +44,7 @@ describe('SearchInput', () => {
});

it('should forward a ref', () => {
const ref = createRef<InputElement>();
const ref = createRef<HTMLInputElement>();
const { container } = render(<SearchInput {...baseProps} ref={ref} />);
const input = container.querySelector('input');
expect(ref.current).toBe(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@

import { useState, type ChangeEvent } from 'react';

import type { InputElement } from '../Input/index.js';

import { SearchInput, type SearchInputProps } from './SearchInput.js';

export default {
Expand All @@ -32,7 +30,7 @@ export const Base = (args: SearchInputProps) => {

const handleChange = ({
target: { value: inputValue },
}: ChangeEvent<InputElement>) => {
}: ChangeEvent<HTMLInputElement>) => {
setValue(inputValue);
};

Expand Down
6 changes: 3 additions & 3 deletions packages/circuit-ui/components/SearchInput/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { forwardRef, useRef } from 'react';
import { Search } from '@sumup-oss/icons';

import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { Input, type InputProps } from '../Input/index.js';
import { CloseButton } from '../CloseButton/index.js';
import {
AccessibilityError,
Expand Down Expand Up @@ -49,9 +49,9 @@ export type SearchInputProps = InputProps & ClearProps;
/**
* SearchInput component for forms.
*/
export const SearchInput = forwardRef<InputElement, SearchInputProps>(
export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
({ value, onClear, clearLabel, inputClassName, ...props }, ref) => {
const localRef = useRef<InputElement>(null);
const localRef = useRef<HTMLInputElement>(null);

if (
process.env.NODE_ENV !== 'production' &&
Expand Down
34 changes: 18 additions & 16 deletions packages/circuit-ui/components/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,43 @@

'use client';

import { forwardRef, useRef } from 'react';
import { forwardRef, useRef, type TextareaHTMLAttributes } from 'react';

import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { Input, type BaseInputProps } from '../Input/index.js';
import { applyMultipleRefs } from '../../util/refs.js';
import { clsx } from '../../styles/clsx.js';

import { useAutoExpand } from './useAutoExpand.js';
import classes from './TextArea.module.css';

export type TextAreaProps = Omit<InputProps, 'rows'> & {
/**
* The number of visible text lines for the control.
* If set to `auto`, the control will auto-expand vertically to show the whole value.
*/
rows?: InputProps['rows'] | 'auto';
/**
* Define the minimum number of visible text lines for the control.
* Works only when `rows` is set to `auto`.
*/
minRows?: InputProps['rows'];
};
export type TextAreaProps = BaseInputProps &
Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'rows'> & {
/**
* The number of visible text lines for the control.
* If set to `auto`, the control will auto-expand vertically to show the whole value.
*/
rows?: number | 'auto';
/**
* Define the minimum number of visible text lines for the control.
* Works only when `rows` is set to `auto`.
*/
minRows?: number;
};

/**
* TextArea component for forms.
*/
export const TextArea = forwardRef<InputElement, TextAreaProps>(
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ inputClassName, ...props }, ref) => {
const localRef = useRef<InputElement>(null);
const localRef = useRef<HTMLTextAreaElement>(null);
const modifiedProps = useAutoExpand(localRef, props);

return (
<Input
{...modifiedProps}
inputClassName={clsx(classes.base, inputClassName)}
as="textarea"
// @ts-expect-error The input is rendered as a `textarea` element above.
ref={applyMultipleRefs(localRef, ref)}
/>
);
Expand Down
Loading

0 comments on commit 8c509d1

Please sign in to comment.