Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Use invalid prop for most inputs #1240

Merged
merged 13 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/css/src/components/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@
@include reset;
}

.ams-select:invalid,
.ams-select[aria-invalid="true"] {
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved
box-shadow: var(--ams-select-invalid-box-shadow);

&:hover {
box-shadow: var(--ams-select-invalid-hover-box-shadow);
}
}

.ams-select:disabled {
Expand All @@ -55,6 +52,11 @@
}
}

.ams-select:invalid:hover,
.ams-select[aria-invalid="true"]:hover {
box-shadow: var(--ams-select-invalid-hover-box-shadow);
}

.ams-select__option:disabled {
color: var(--ams-select-option-disabled-color);
}
8 changes: 4 additions & 4 deletions packages/react/src/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type CheckboxProps = {
invalid?: boolean
/** Allows being neither checked nor unchecked. */
indeterminate?: boolean
} & PropsWithChildren<InputHTMLAttributes<HTMLInputElement>>
} & PropsWithChildren<Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-invalid' | 'type'>>
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved

export const Checkbox = forwardRef(
(
Expand All @@ -38,11 +38,11 @@ export const Checkbox = forwardRef(
<div className={clsx('ams-checkbox', className)}>
<input
{...restProps}
type="checkbox"
aria-invalid={invalid || undefined}
className="ams-checkbox__input"
ref={innerRef}
id={id}
aria-invalid={invalid || undefined}
ref={innerRef}
type="checkbox"
/>
<label className="ams-checkbox__label" htmlFor={id}>
<span className="ams-checkbox__checkmark" />
Expand Down
27 changes: 27 additions & 0 deletions packages/react/src/DateInput/DateInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,31 @@ describe('Date input', () => {

expect(ref.current).toBe(component)
})

// invalid state
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved

it('is not invalid by default', () => {
const { container } = render(<DateInput />)

const component = container.querySelector(':only-child')

expect(component).not.toBeInvalid()
})

it('can have an invalid state', () => {
const { container } = render(<DateInput invalid />)

const component = container.querySelector(':only-child')

expect(component).toHaveAttribute('aria-invalid')
expect(component).toBeInvalid()
})

it('omits non-essential invalid attributes when not invalid', () => {
const { container } = render(<DateInput invalid={false} />)

const component = container.querySelector(':only-child')

expect(component).not.toHaveAttribute('aria-invalid')
})
})
15 changes: 12 additions & 3 deletions packages/react/src/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, InputHTMLAttributes } from 'react'

export type DateInputProps = InputHTMLAttributes<HTMLInputElement>
export type DateInputProps = {
/** Whether the value fails a validation rule. */
invalid?: boolean
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-invalid' | 'type'>

export const DateInput = forwardRef(
({ className, ...restProps }: DateInputProps, ref: ForwardedRef<HTMLInputElement>) => (
<input {...restProps} ref={ref} className={clsx('ams-date-input', className)} type="date" />
({ className, invalid, ...restProps }: DateInputProps, ref: ForwardedRef<HTMLInputElement>) => (
<input
{...restProps}
aria-invalid={invalid || undefined}
RubenSibon marked this conversation as resolved.
Show resolved Hide resolved
className={clsx('ams-date-input', className)}
ref={ref}
type="date"
/>
),
)

Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { ForwardedRef, InputHTMLAttributes, PropsWithChildren } from 'react
export type RadioProps = {
/** Whether the value fails a validation rule. */
invalid?: boolean
} & PropsWithChildren<InputHTMLAttributes<HTMLInputElement>>
} & PropsWithChildren<Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-invalid' | 'type'>>

export const Radio = forwardRef(
({ children, className, invalid, ...restProps }: RadioProps, ref: ForwardedRef<HTMLInputElement>) => {
Expand All @@ -22,11 +22,11 @@ export const Radio = forwardRef(
<div className={clsx('ams-radio', className)}>
<input
{...restProps}
type="radio"
aria-invalid={invalid || undefined}
className="ams-radio__input"
ref={ref}
id={id}
aria-invalid={invalid || undefined}
ref={ref}
type="radio"
/>
<label className="ams-radio__label" htmlFor={id}>
<span className="ams-radio__circle" />
Expand Down
23 changes: 21 additions & 2 deletions packages/react/src/Select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,31 @@ describe('Select', () => {
expect(component).toBeDisabled()
})

it('can be invalid', () => {
// invalid state

it('is not invalid by default', () => {
render(<Select />)

const component = screen.getByRole('combobox')

expect(component).not.toBeInvalid()
})

it('can have an invalid state', () => {
render(<Select invalid />)

const component = screen.getByRole('combobox')

expect(component).toHaveClass('ams-select--invalid')
expect(component).toHaveAttribute('aria-invalid')
expect(component).toBeInvalid()
})

it('omits non-essential invalid attributes when not invalid', () => {
render(<Select invalid={false} />)

const component = screen.getByRole('combobox')

expect(component).not.toHaveAttribute('aria-invalid')
})

it('is not required by default', () => {
Expand Down
9 changes: 2 additions & 7 deletions packages/react/src/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,11 @@ import { SelectOptionGroup } from './SelectOptionGroup'
export type SelectProps = {
/** Whether the value fails a validation rule. */
invalid?: boolean
} & PropsWithChildren<SelectHTMLAttributes<HTMLSelectElement>>
} & PropsWithChildren<Omit<SelectHTMLAttributes<HTMLSelectElement>, 'aria-invalid'>>

const SelectRoot = forwardRef(
({ children, className, invalid, ...restProps }: SelectProps, ref: ForwardedRef<HTMLSelectElement>) => (
<select
{...restProps}
ref={ref}
className={clsx('ams-select', invalid && 'ams-select--invalid', className)}
aria-invalid={invalid || undefined}
>
<select {...restProps} aria-invalid={invalid || undefined} className={clsx('ams-select', className)} ref={ref}>
{children}
</select>
),
Expand Down
37 changes: 32 additions & 5 deletions packages/react/src/TextArea/TextArea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,46 @@ describe('Text area', () => {
expect(ref.current).toBe(component)
})

it('renders bidirectional by default using `dir="auto"`', () => {
// invalid state

it('is not invalid by default', () => {
render(<TextArea />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'auto')
expect(component).not.toBeInvalid()
})

it('renders left-to-right by using `dir="ltr"`', () => {
render(<TextArea dir="ltr" />)
it('can have an invalid state', () => {
render(<TextArea invalid />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'ltr')
expect(component).toHaveAttribute('aria-invalid')
expect(component).toBeInvalid()
})

it('omits non-essential invalid attributes when not invalid', () => {
render(<TextArea invalid={false} />)

const component = screen.getByRole('textbox')

expect(component).not.toHaveAttribute('aria-invalid')
})
})

it('renders bidirectional by default using `dir="auto"`', () => {
render(<TextArea />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'auto')
})

it('renders left-to-right by using `dir="ltr"`', () => {
render(<TextArea dir="ltr" />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'ltr')
})
11 changes: 7 additions & 4 deletions packages/react/src/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,26 @@ import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, TextareaHTMLAttributes } from 'react'

export type TextAreaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
export type TextAreaProps = {
/** Whether the value fails a validation rule. */
invalid?: boolean
/** Allows the user to resize the text box. The default is resizing in both directions. */
resize?: 'none' | 'horizontal' | 'vertical'
}
} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'aria-invalid'>

export const TextArea = forwardRef(
({ className, dir, resize, ...restProps }: TextAreaProps, ref: ForwardedRef<HTMLTextAreaElement>) => (
({ className, dir, invalid, resize, ...restProps }: TextAreaProps, ref: ForwardedRef<HTMLTextAreaElement>) => (
<textarea
{...restProps}
ref={ref}
aria-invalid={invalid || undefined}
className={clsx(
'ams-text-area',
resize && `ams-text-area--resize-${resize}`,
restProps.cols && 'ams-text-area--cols',
className,
)}
dir={dir ?? 'auto'}
ref={ref}
/>
),
)
Expand Down
37 changes: 32 additions & 5 deletions packages/react/src/TextInput/TextInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,46 @@ describe('Text input', () => {
expect(ref.current).toBe(component)
})

it('renders bidirectional by default using `dir="auto"`', () => {
// invalid state

it('is not invalid by default', () => {
render(<TextInput />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'auto')
expect(component).not.toBeInvalid()
})

it('renders left-to-right by using `dir="ltr"`', () => {
render(<TextInput dir="ltr" />)
it('can have an invalid state', () => {
render(<TextInput invalid />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'ltr')
expect(component).toHaveAttribute('aria-invalid')
expect(component).toBeInvalid()
})

it('omits non-essential invalid attributes when not invalid', () => {
render(<TextInput invalid={false} />)

const component = screen.getByRole('textbox')

expect(component).not.toHaveAttribute('aria-invalid')
})
})

it('renders bidirectional by default using `dir="auto"`', () => {
render(<TextInput />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'auto')
})

it('renders left-to-right by using `dir="ltr"`', () => {
render(<TextInput dir="ltr" />)

const component = screen.getByRole('textbox')

expect(component).toHaveAttribute('dir', 'ltr')
})
16 changes: 13 additions & 3 deletions packages/react/src/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, InputHTMLAttributes } from 'react'

export type TextInputProps = InputHTMLAttributes<HTMLInputElement>
export type TextInputProps = {
/** Whether the value fails a validation rule. */
invalid?: boolean
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-invalid'>

export const TextInput = forwardRef(
({ className, dir, ...restProps }: TextInputProps, ref: ForwardedRef<HTMLInputElement>) => (
<input {...restProps} className={clsx('ams-text-input', className)} dir={dir ?? 'auto'} ref={ref} type="text" />
({ className, dir, invalid, ...restProps }: TextInputProps, ref: ForwardedRef<HTMLInputElement>) => (
<input
{...restProps}
aria-invalid={invalid || undefined}
className={clsx('ams-text-input', className)}
dir={dir ?? 'auto'}
ref={ref}
type="text"
/>
),
)

Expand Down
27 changes: 27 additions & 0 deletions packages/react/src/TimeInput/TimeInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,31 @@ describe('Time input', () => {

expect(ref.current).toBe(component)
})

// invalid state

it('is not invalid by default', () => {
const { container } = render(<TimeInput />)

const component = container.querySelector(':only-child')

expect(component).not.toBeInvalid()
})

it('can have an invalid state', () => {
const { container } = render(<TimeInput invalid />)

const component = container.querySelector(':only-child')

expect(component).toHaveAttribute('aria-invalid')
expect(component).toBeInvalid()
})

it('omits non-essential invalid attributes when not invalid', () => {
const { container } = render(<TimeInput invalid={false} />)

const component = container.querySelector(':only-child')

expect(component).not.toHaveAttribute('aria-invalid')
})
})
15 changes: 12 additions & 3 deletions packages/react/src/TimeInput/TimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, InputHTMLAttributes } from 'react'

export type TimeInputProps = InputHTMLAttributes<HTMLInputElement>
export type TimeInputProps = {
/** Whether the value fails a validation rule. */
invalid?: boolean
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-invalid' | 'type'>

export const TimeInput = forwardRef(
({ className, ...restProps }: TimeInputProps, ref: ForwardedRef<HTMLInputElement>) => (
<input {...restProps} ref={ref} className={clsx('ams-time-input', className)} type="time" />
({ className, invalid, ...restProps }: TimeInputProps, ref: ForwardedRef<HTMLInputElement>) => (
<input
{...restProps}
aria-invalid={invalid || undefined}
className={clsx('ams-time-input', className)}
ref={ref}
type="time"
/>
),
)

Expand Down
Loading
Loading