Browse Source

Update text controls

Add length prop for size.
master
TheoryOfNekomata 1 year ago
parent
commit
26a43a6a98
12 changed files with 131 additions and 24 deletions
  1. +7
    -0
      categories/choice/react/src/components/ComboBox/index.tsx
  2. +22
    -1
      categories/formatted/react/src/components/EmailInput/index.tsx
  3. +22
    -22
      categories/formatted/react/src/components/PatternTextInput/PatternTextInput.test.tsx
  4. +11
    -1
      categories/formatted/react/src/components/PatternTextInput/index.tsx
  5. +8
    -0
      categories/formatted/react/src/components/PhoneNumberInput/index.tsx
  6. +9
    -0
      categories/formatted/react/src/components/UrlInput/index.tsx
  7. +7
    -0
      categories/freeform/react/src/components/MaskedTextInput/index.tsx
  8. +7
    -0
      categories/freeform/react/src/components/TextInput/index.tsx
  9. +4
    -0
      categories/multichoice/react/src/components/TagInput/index.tsx
  10. +7
    -0
      categories/number/react/src/components/NumberSpinner/index.tsx
  11. +7
    -0
      categories/temporal/react/src/components/DateDropdown/index.tsx
  12. +20
    -0
      showcases/web-kitchensink-reactnext/src/pages/categories/formatted/index.tsx

+ 7
- 0
categories/choice/react/src/components/ComboBox/index.tsx View File

@@ -51,6 +51,10 @@ export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedEleme
* Input mode of the component. * Input mode of the component.
*/ */
inputMode?: TextControl.InputMode, inputMode?: TextControl.InputMode,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -74,6 +78,7 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>(
inputMode = 'text' as const, inputMode = 'text' as const,
id: idProp, id: idProp,
style, style,
length,
...etcProps ...etcProps
}: ComboBoxProps, }: ComboBoxProps,
forwardedRef, forwardedRef,
@@ -112,6 +117,7 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>(
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
id={id} id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
@@ -245,4 +251,5 @@ ComboBox.defaultProps = {
variant: 'default' as const, variant: 'default' as const,
hiddenLabel: false as const, hiddenLabel: false as const,
inputMode: 'text' as const, inputMode: 'text' as const,
length: undefined,
}; };

+ 22
- 1
categories/formatted/react/src/components/EmailInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type EmailInputDerivedElement = HTMLInputElement; export type EmailInputDerivedElement = HTMLInputElement;


export interface EmailInputProps extends Omit<React.HTMLProps<EmailInputDerivedElement>, 'size' | 'type' | 'label' | 'inputMode'> {
export interface EmailInputProps extends Omit<React.HTMLProps<EmailInputDerivedElement>, 'size' | 'type' | 'label' | 'inputMode' | 'pattern'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -37,6 +37,14 @@ export interface EmailInputProps extends Omit<React.HTMLProps<EmailInputDerivedE
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Allowed domains for emails.
*/
domains?: string[],
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -56,6 +64,8 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
className, className,
id: idProp, id: idProp,
style, style,
domains = [],
length,
...etcProps ...etcProps
}: EmailInputProps, }: EmailInputProps,
forwardedRef, forwardedRef,
@@ -64,6 +74,12 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
const defaultId = React.useId(); const defaultId = React.useId();
const id = idProp ?? defaultId; const id = idProp ?? defaultId;


const pattern = (
Array.isArray(domains) && domains.length > 0
? `.+?@(${domains.join('|').replace(/\./g, '\\.')})$`
: undefined
);

return ( return (
<div <div
className={clsx( className={clsx(
@@ -79,11 +95,13 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
id={id} id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
type="email" type="email"
data-testid="input" data-testid="input"
pattern={pattern}
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block font-inherit', 'bg-negative rounded-inherit w-full peer block font-inherit',
'focus:outline-0', 'focus:outline-0',
@@ -173,6 +191,7 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
)} )}
{indicator && ( {indicator && (
<div <div
data-testid="indicator"
className={clsx( className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none', 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{ {
@@ -207,4 +226,6 @@ EmailInput.defaultProps = {
block: false, block: false,
variant: 'default', variant: 'default',
hiddenLabel: false, hiddenLabel: false,
domains: [],
length: undefined,
}; };

categories/formatted/react/src/components/PatternTextInput/TextInput.test.tsx → categories/formatted/react/src/components/PatternTextInput/PatternTextInput.test.tsx View File

@@ -15,20 +15,20 @@ import {
} from 'vitest'; } from 'vitest';
import matchers from '@testing-library/jest-dom/matchers'; import matchers from '@testing-library/jest-dom/matchers';
import { import {
TextInput,
TextInputDerivedElement,
PatternTextInput,
PatternTextInputDerivedElement,
} from '.'; } from '.';


expect.extend(matchers); expect.extend(matchers);


describe('TextInput', () => {
describe('PatternTextInput', () => {
afterEach(() => { afterEach(() => {
cleanup(); cleanup();
}); });


it('renders a textbox', () => { it('renders a textbox', () => {
render( render(
<TextInput />,
<PatternTextInput />,
); );
const textbox = screen.getByRole('textbox'); const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument(); expect(textbox).toBeInTheDocument();
@@ -37,7 +37,7 @@ describe('TextInput', () => {


it('renders a border', () => { it('renders a border', () => {
render( render(
<TextInput
<PatternTextInput
border border
/>, />,
); );
@@ -47,7 +47,7 @@ describe('TextInput', () => {


it('renders a label', () => { it('renders a label', () => {
render( render(
<TextInput
<PatternTextInput
label="foo" label="foo"
/>, />,
); );
@@ -59,7 +59,7 @@ describe('TextInput', () => {


it('renders a hidden label', () => { it('renders a hidden label', () => {
render( render(
<TextInput
<PatternTextInput
label="foo" label="foo"
hiddenLabel hiddenLabel
/>, />,
@@ -73,7 +73,7 @@ describe('TextInput', () => {


it('renders a hint', () => { it('renders a hint', () => {
render( render(
<TextInput
<PatternTextInput
hint="foo" hint="foo"
/>, />,
); );
@@ -83,7 +83,7 @@ describe('TextInput', () => {


it('renders an indicator', () => { it('renders an indicator', () => {
render( render(
<TextInput
<PatternTextInput
indicator={ indicator={
<div /> <div />
} }
@@ -111,7 +111,7 @@ describe('TextInput', () => {
}) => { }) => {
it('renders input styles', () => { it('renders input styles', () => {
render( render(
<TextInput
<PatternTextInput
size={size} size={size}
/>, />,
); );
@@ -122,7 +122,7 @@ describe('TextInput', () => {


it('renders label styles with indicator', () => { it('renders label styles with indicator', () => {
render( render(
<TextInput
<PatternTextInput
size={size} size={size}
label="foo" label="foo"
indicator={<div />} indicator={<div />}
@@ -134,7 +134,7 @@ describe('TextInput', () => {


it('renders hint styles when indicator is present', () => { it('renders hint styles when indicator is present', () => {
render( render(
<TextInput
<PatternTextInput
size={size} size={size}
hint="hint" hint="hint"
indicator={<div />} indicator={<div />}
@@ -147,7 +147,7 @@ describe('TextInput', () => {


it('renders indicator styles', () => { it('renders indicator styles', () => {
render( render(
<TextInput
<PatternTextInput
size={size} size={size}
indicator={ indicator={
<div /> <div />
@@ -162,7 +162,7 @@ describe('TextInput', () => {


it('renders a block textbox', () => { it('renders a block textbox', () => {
render( render(
<TextInput
<PatternTextInput
block block
/>, />,
); );
@@ -173,7 +173,7 @@ describe('TextInput', () => {


it.each(TextControl.AVAILABLE_INPUT_TYPES)('renders a textbox with type %s', (inputType) => { it.each(TextControl.AVAILABLE_INPUT_TYPES)('renders a textbox with type %s', (inputType) => {
render( render(
<TextInput
<PatternTextInput
type={inputType} type={inputType}
/>, />,
); );
@@ -183,7 +183,7 @@ describe('TextInput', () => {


it('falls back to text input mode when it clashes with the input type', () => { it('falls back to text input mode when it clashes with the input type', () => {
render( render(
<TextInput
<PatternTextInput
type="text" type="text"
inputMode="search" inputMode="search"
/>, />,
@@ -207,7 +207,7 @@ describe('TextInput', () => {
}) => { }) => {
it('renders input styles', () => { it('renders input styles', () => {
render( render(
<TextInput
<PatternTextInput
variant={variant} variant={variant}
/>, />,
); );
@@ -218,7 +218,7 @@ describe('TextInput', () => {


it('renders hint styles', () => { it('renders hint styles', () => {
render( render(
<TextInput
<PatternTextInput
variant={variant} variant={variant}
hint="hint" hint="hint"
/>, />,
@@ -231,12 +231,12 @@ describe('TextInput', () => {


it('handles change events', async () => { it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce( const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<TextInputDerivedElement>) => {
(e: React.ChangeEvent<PatternTextInputDerivedElement>) => {
e.preventDefault(); e.preventDefault();
}, },
); );
render( render(
<TextInput
<PatternTextInput
onChange={onChange} onChange={onChange}
/>, />,
); );
@@ -247,12 +247,12 @@ describe('TextInput', () => {


it('handles input events', async () => { it('handles input events', async () => {
const onInput = vi.fn().mockImplementationOnce( const onInput = vi.fn().mockImplementationOnce(
(e: React.SyntheticEvent<TextInputDerivedElement>) => {
(e: React.SyntheticEvent<PatternTextInputDerivedElement>) => {
e.preventDefault(); e.preventDefault();
}, },
); );
render( render(
<TextInput
<PatternTextInput
onInput={onInput} onInput={onInput}
/>, />,
); );

+ 11
- 1
categories/formatted/react/src/components/PatternTextInput/index.tsx View File

@@ -45,6 +45,10 @@ export interface PatternTextInputProps extends Omit<React.HTMLProps<PatternTextI
* Input mode of the component. * Input mode of the component.
*/ */
inputMode?: TextControl.InputMode, inputMode?: TextControl.InputMode,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -52,7 +56,10 @@ export interface PatternTextInputProps extends Omit<React.HTMLProps<PatternTextI
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
export const PatternTextInput = React.forwardRef<PatternTextInputDerivedElement, PatternTextInputProps>((
export const PatternTextInput = React.forwardRef<
PatternTextInputDerivedElement,
PatternTextInputProps
>((
{ {
label, label,
hint, hint,
@@ -67,6 +74,7 @@ export const PatternTextInput = React.forwardRef<PatternTextInputDerivedElement,
id: idProp, id: idProp,
style, style,
inputMode = type, inputMode = type,
length,
...etcProps ...etcProps
}, },
forwardedRef, forwardedRef,
@@ -98,6 +106,7 @@ export const PatternTextInput = React.forwardRef<PatternTextInputDerivedElement,
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
aria-labelledby={labelId} aria-labelledby={labelId}
id={id} id={id}
@@ -229,4 +238,5 @@ PatternTextInput.defaultProps = {
variant: 'default', variant: 'default',
hiddenLabel: false, hiddenLabel: false,
inputMode: 'text', inputMode: 'text',
length: undefined,
}; };

+ 8
- 0
categories/formatted/react/src/components/PhoneNumberInput/index.tsx View File

@@ -47,6 +47,10 @@ export interface PhoneNumberInputProps extends Omit<React.HTMLProps<PhoneNumberI
* Country where the phone number should be formatted for. * Country where the phone number should be formatted for.
*/ */
country?: Country, country?: Country,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -73,6 +77,7 @@ export const PhoneNumberInput = React.forwardRef<
value, value,
onChange, onChange,
name, name,
length,
...etcProps ...etcProps
}: PhoneNumberInputProps, }: PhoneNumberInputProps,
forwardedRef, forwardedRef,
@@ -140,9 +145,11 @@ export const PhoneNumberInput = React.forwardRef<
className, className,
)} )}
style={style} style={style}
data-testid="base"
> >
<input <input
{...etcProps} {...etcProps}
size={length}
value={value} value={value}
onChange={onChange} onChange={onChange}
ref={ref} ref={ref}
@@ -222,6 +229,7 @@ export const PhoneNumberInput = React.forwardRef<
)} )}
{indicator && ( {indicator && (
<div <div
data-testid="indicator"
className={clsx( className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none', 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{ {


+ 9
- 0
categories/formatted/react/src/components/UrlInput/index.tsx View File

@@ -37,6 +37,10 @@ export interface UrlInputProps extends Omit<React.HTMLProps<UrlInputDerivedEleme
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -54,6 +58,7 @@ export const UrlInput = React.forwardRef<UrlInputDerivedElement, UrlInputProps>(
hiddenLabel = false, hiddenLabel = false,
className, className,
style, style,
length,
id: idProp, id: idProp,
...etcProps ...etcProps
}: UrlInputProps, }: UrlInputProps,
@@ -75,9 +80,11 @@ export const UrlInput = React.forwardRef<UrlInputDerivedElement, UrlInputProps>(
className, className,
)} )}
style={style} style={style}
data-testid="base"
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
id={id} id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
@@ -172,6 +179,7 @@ export const UrlInput = React.forwardRef<UrlInputDerivedElement, UrlInputProps>(
)} )}
{indicator && ( {indicator && (
<div <div
data-testid="indicator"
className={clsx( className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none', 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{ {
@@ -205,4 +213,5 @@ UrlInput.defaultProps = {
block: false, block: false,
variant: 'default', variant: 'default',
hiddenLabel: false, hiddenLabel: false,
length: undefined,
}; };

+ 7
- 0
categories/freeform/react/src/components/MaskedTextInput/index.tsx View File

@@ -37,6 +37,10 @@ export interface MaskedTextInputProps extends Omit<React.HTMLProps<MaskedTextInp
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -58,6 +62,7 @@ export const MaskedTextInput = React.forwardRef<
className, className,
id: idProp, id: idProp,
style, style,
length,
...etcProps ...etcProps
}: MaskedTextInputProps, }: MaskedTextInputProps,
forwardedRef, forwardedRef,
@@ -82,6 +87,7 @@ export const MaskedTextInput = React.forwardRef<
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
id={id} id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
@@ -210,4 +216,5 @@ MaskedTextInput.defaultProps = {
block: false, block: false,
variant: 'default', variant: 'default',
hiddenLabel: false, hiddenLabel: false,
length: undefined,
}; };

+ 7
- 0
categories/freeform/react/src/components/TextInput/index.tsx View File

@@ -45,6 +45,10 @@ export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedEle
* Input mode of the component. * Input mode of the component.
*/ */
inputMode?: TextControl.InputMode, inputMode?: TextControl.InputMode,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -67,6 +71,7 @@ export const TextInput = React.forwardRef<TextInputDerivedElement, TextInputProp
id: idProp, id: idProp,
style, style,
inputMode = type, inputMode = type,
length,
...etcProps ...etcProps
}, },
forwardedRef, forwardedRef,
@@ -98,6 +103,7 @@ export const TextInput = React.forwardRef<TextInputDerivedElement, TextInputProp
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
aria-labelledby={labelId} aria-labelledby={labelId}
id={id} id={id}
@@ -229,4 +235,5 @@ TextInput.defaultProps = {
variant: 'default', variant: 'default',
hiddenLabel: false, hiddenLabel: false,
inputMode: 'text', inputMode: 'text',
length: undefined,
}; };

+ 4
- 0
categories/multichoice/react/src/components/TagInput/index.tsx View File

@@ -55,6 +55,9 @@ export interface TagInputProps extends Omit<React.HTMLProps<TagInputDerivedEleme
* Separators for splitting the input value into multiple tags. * Separators for splitting the input value into multiple tags.
*/ */
separator?: TagInputSeparator[], separator?: TagInputSeparator[],
/**
* Should the last tag be editable when removed?
*/
editOnRemove?: boolean, editOnRemove?: boolean,
} }


@@ -221,6 +224,7 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
variant === 'alternate' && 'tag-input-alternate', variant === 'alternate' && 'tag-input-alternate',
className, className,
)} )}
data-testid="base"
onFocusCapture={handleFocusCapture} onFocusCapture={handleFocusCapture}
> >
<textarea <textarea


+ 7
- 0
categories/number/react/src/components/NumberSpinner/index.tsx View File

@@ -37,6 +37,10 @@ export interface NumberSpinnerProps extends Omit<React.HTMLProps<NumberSpinnerDe
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -55,6 +59,7 @@ export const NumberSpinner = React.forwardRef<NumberSpinnerDerivedElement, Numbe
className, className,
id: idProp, id: idProp,
style, style,
length,
...etcProps ...etcProps
}: NumberSpinnerProps, }: NumberSpinnerProps,
forwardedRef, forwardedRef,
@@ -79,6 +84,7 @@ export const NumberSpinner = React.forwardRef<NumberSpinnerDerivedElement, Numbe
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
id={id} id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
@@ -208,4 +214,5 @@ NumberSpinner.defaultProps = {
hiddenLabel: false, hiddenLabel: false,
size: 'medium', size: 'medium',
variant: 'default', variant: 'default',
length: undefined,
}; };

+ 7
- 0
categories/temporal/react/src/components/DateDropdown/index.tsx View File

@@ -37,6 +37,10 @@ export interface DateDropdownProps extends Omit<React.HTMLProps<DateDropdownDeri
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Visual length of the input.
*/
length?: number,
} }


/** /**
@@ -58,6 +62,7 @@ export const DateDropdown = React.forwardRef<
className, className,
id: idProp, id: idProp,
style, style,
length,
...etcProps ...etcProps
}: DateDropdownProps, }: DateDropdownProps,
forwardedRef, forwardedRef,
@@ -82,6 +87,7 @@ export const DateDropdown = React.forwardRef<
> >
<input <input
{...etcProps} {...etcProps}
size={length}
ref={forwardedRef} ref={forwardedRef}
id={id} id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
@@ -211,4 +217,5 @@ DateDropdown.defaultProps = {
block: false, block: false,
variant: 'default', variant: 'default',
hiddenLabel: false, hiddenLabel: false,
length: undefined,
}; };

+ 20
- 0
showcases/web-kitchensink-reactnext/src/pages/categories/formatted/index.tsx View File

@@ -19,6 +19,26 @@ const TemporalPage: NextPage = () => {
/> />
</Subsection> </Subsection>
</Section> </Section>
<Section title="EmailInput">
<Subsection title="Default">
<Formatted.EmailInput
label="Email"
name="email"
border
/>
</Subsection>
<Subsection title="With domains">
<Formatted.EmailInput
label="Email"
name="email"
border
variant="alternate"
domains={['gmail.com', 'yahoo.com']}
hint="Only GMail or Yahoo Mail allowed"
length={50}
/>
</Subsection>
</Section>
</DefaultLayout> </DefaultLayout>
) )
} }


Loading…
Cancel
Save