Skip to content

Commit

Permalink
fix: Use JSX.ImplicitElements to derive valid property names (#267)
Browse files Browse the repository at this point in the history
* Use JSX.ImplicitElements to derive valid property names

* Use name of attribute only when value is true
  • Loading branch information
ahobson authored and haworku committed Jul 13, 2020
1 parent 5f8442f commit 2eba792
Show file tree
Hide file tree
Showing 33 changed files with 74 additions and 59 deletions.
2 changes: 1 addition & 1 deletion docs/adding_new_components.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Pay special attention to:
}

export const Form = (
props: FormProps & React.FormHTMLAttributes<HTMLFormElement>
props: FormProps & JSX.IntrinsicElements['form']
): React.ReactElement =>
```

Expand Down
6 changes: 6 additions & 0 deletions src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ export const customClass = (): React.ReactElement => (
Click Me
</Button>
)

export const disabled = (): React.ReactElement => (
<Button type="button" disabled>
Click Me
</Button>
)
1 change: 0 additions & 1 deletion src/components/Button/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ describe('Button component', () => {
)

fireEvent.click(getByText('Click Me'))

expect(onClickFn).toHaveBeenCalledTimes(1)
})

Expand Down
9 changes: 4 additions & 5 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { deprecationWarning } from '../../deprecation'

interface ButtonProps {
type: 'button' | 'submit' | 'reset'
disabled?: boolean
children: React.ReactNode
secondary?: boolean
base?: boolean
Expand All @@ -25,11 +24,10 @@ interface ButtonProps {
}

export const Button = (
props: ButtonProps & React.HTMLAttributes<HTMLButtonElement>
props: ButtonProps & JSX.IntrinsicElements['button']
): React.ReactElement => {
const {
type,
disabled,
children,
secondary,
base,
Expand All @@ -43,6 +41,7 @@ export const Button = (
unstyled,
onClick,
className,
...defaultProps
} = props

if (big) {
Expand Down Expand Up @@ -75,9 +74,9 @@ export const Button = (
<button
type={type}
className={classes}
disabled={disabled}
onClick={onClick}
data-testid="button">
data-testid="button"
{...defaultProps}>
{children}
</button>
)
Expand Down
4 changes: 1 addition & 3 deletions src/components/Footer/SocialLinks/SocialLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ type SocialLinksProps = {
links: React.ReactNode[]
}

export const SocialLinks = (
props: SocialLinksProps & React.HTMLAttributes<HTMLElement>
): React.ReactElement => {
export const SocialLinks = (props: SocialLinksProps): React.ReactElement => {
const { links } = props

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/GovBanner/GovBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import dotGovIcon from 'uswds/src/img/icon-dot-gov.svg'
import httpsIcon from 'uswds/src/img/icon-https.svg'

export const GovBanner = (
props: React.HTMLAttributes<HTMLElement>
props: JSX.IntrinsicElements['section']
): React.ReactElement => {
const { className, ...sectionProps } = props
const [isOpen, setOpenState] = useState(false)
Expand Down
4 changes: 2 additions & 2 deletions src/components/Search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classnames from 'classnames'
import { deprecationWarning } from '../../deprecation'

import { Button } from '../Button/Button'
import { Form } from '../forms/Form/Form'
import { Form, OptionalFormProps } from '../forms/Form/Form'
import { Label } from '../forms/Label/Label'
import { TextInput } from '../forms/TextInput/TextInput'

Expand All @@ -25,7 +25,7 @@ interface SearchInputProps {
}

export const Search = (
props: SearchInputProps & React.FormHTMLAttributes<HTMLFormElement>
props: SearchInputProps & OptionalFormProps
): React.ReactElement => {
const {
onSubmit,
Expand Down
10 changes: 7 additions & 3 deletions src/components/Tag/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ interface TagProps {
}

export const Tag = (
props: TagProps & React.HTMLAttributes<HTMLSpanElement>
props: TagProps & JSX.IntrinsicElements['span']
): React.ReactElement => {
const { children, background, className } = props
const { children, background, className, ...spanProps } = props

const style: React.CSSProperties = {}
if (background) {
Expand All @@ -19,7 +19,11 @@ export const Tag = (
const tagClasses = classnames('usa-tag', className)

return (
<span data-testid="tag" className={tagClasses} style={{ ...style }}>
<span
data-testid="tag"
className={tagClasses}
style={{ ...style }}
{...spanProps}>
{children}
</span>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/card/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface CardProps {
}

export const Card = (
props: CardProps & React.HTMLAttributes<HTMLLIElement> & GridLayoutProp
props: CardProps & JSX.IntrinsicElements['li'] & GridLayoutProp
): React.ReactElement => {
const {
layout = 'standardDefault',
Expand Down
2 changes: 1 addition & 1 deletion src/components/card/CardBody/CardBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import classnames from 'classnames'

export const CardBody = (
props: { exdent?: boolean } & React.HTMLAttributes<HTMLDivElement>
props: { exdent?: boolean } & JSX.IntrinsicElements['div']
): React.ReactElement => {
const { exdent, children, className, ...bodyProps } = props

Expand Down
2 changes: 1 addition & 1 deletion src/components/card/CardFooter/CardFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import classnames from 'classnames'

export const CardFooter = (
props: { exdent?: boolean } & React.HTMLAttributes<HTMLDivElement>
props: { exdent?: boolean } & JSX.IntrinsicElements['div']
): React.ReactElement => {
const { exdent, children, className, ...footerProps } = props

Expand Down
2 changes: 1 addition & 1 deletion src/components/card/CardGroup/CardGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import classnames from 'classnames'

export const CardGroup = (
props: React.HTMLAttributes<HTMLUListElement>
props: JSX.IntrinsicElements['ul']
): React.ReactElement => {
const { children, className, ...ulProps } = props

Expand Down
2 changes: 1 addition & 1 deletion src/components/card/CardHeader/CardHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import classnames from 'classnames'

export const CardHeader = (
props: { exdent?: boolean } & React.HTMLAttributes<HTMLElement>
props: { exdent?: boolean } & JSX.IntrinsicElements['header']
): React.ReactElement => {
const { exdent, children, className, ...headerProps } = props

Expand Down
2 changes: 1 addition & 1 deletion src/components/card/CardMedia/CardMedia.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface CardMediaProps {
}

export const CardMedia = (
props: CardMediaProps & React.HTMLAttributes<HTMLDivElement>
props: CardMediaProps & JSX.IntrinsicElements['div']
): React.ReactElement => {
const {
exdent,
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface CheckboxProps {
}

export const Checkbox = (
props: CheckboxProps & React.InputHTMLAttributes<HTMLInputElement>
props: CheckboxProps & JSX.IntrinsicElements['input']
): React.ReactElement => {
const { id, name, className, label, inputRef, ...inputProps } = props

Expand Down
4 changes: 2 additions & 2 deletions src/components/forms/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import classnames from 'classnames'

import { TextInput } from '../TextInput/TextInput'
import { TextInput, OptionalTextInputProps } from '../TextInput/TextInput'
import { Label } from '../Label/Label'
import { FormGroup } from '../FormGroup/FormGroup'

Expand All @@ -15,7 +15,7 @@ interface DateInputElementProps {
}

export const DateInput = (
props: DateInputElementProps & React.InputHTMLAttributes<HTMLInputElement>
props: DateInputElementProps & OptionalTextInputProps
): React.ReactElement => {
const {
id,
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/DateInputGroup/DateInputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import classnames from 'classnames'

export const DateInputGroup = (
props: React.HTMLAttributes<HTMLElement>
props: JSX.IntrinsicElements['div']
): React.ReactElement => {
const { children, className, ...divAttributes } = props

Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface DropdownProps {
}

export const Dropdown = (
props: DropdownProps & React.SelectHTMLAttributes<HTMLSelectElement>
props: DropdownProps & JSX.IntrinsicElements['select']
): React.ReactElement => {
const { id, name, className, inputRef, children, ...inputProps } = props

Expand Down
13 changes: 9 additions & 4 deletions src/components/forms/Form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import React from 'react'
import classnames from 'classnames'

interface FormProps {
interface RequiredFormProps {
children: React.ReactNode
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
}

interface CustomFormProps {
className?: string
large?: boolean
search?: boolean
}

export const Form = (
props: FormProps & React.FormHTMLAttributes<HTMLFormElement>
): React.ReactElement => {
export type OptionalFormProps = CustomFormProps & JSX.IntrinsicElements['form']

type FormProps = RequiredFormProps & OptionalFormProps

export const Form = (props: FormProps): React.ReactElement => {
const { onSubmit, children, className, large, search, ...formProps } = props

const classes = classnames(
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface RadioProps {
}

export const Radio = (
props: RadioProps & React.InputHTMLAttributes<HTMLInputElement>
props: RadioProps & JSX.IntrinsicElements['input']
): React.ReactElement => {
const { id, name, className, label, inputRef, ...inputProps } = props

Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/RangeInput/RangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface RangeInputProps {
}

export const RangeInput = (
props: RangeInputProps & React.InputHTMLAttributes<HTMLInputElement>
props: RangeInputProps & JSX.IntrinsicElements['input']
): React.ReactElement => {
// Range defaults to min = 0, max = 100, step = 1, and value = (max/2) if not specified.
const { className, inputRef, ...inputProps } = props
Expand Down
14 changes: 10 additions & 4 deletions src/components/forms/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import React from 'react'
import classnames from 'classnames'
import { deprecationWarning } from '../../../deprecation'

interface TextInputProps {
interface RequiredTextInputProps {
id: string
name: string
type: 'text' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'url'
}

interface CustomTextInputProps {
className?: string
validationStatus?: 'error' | 'success'
/**
Expand Down Expand Up @@ -33,9 +36,12 @@ interface TextInputProps {
| undefined
}

export const TextInput = (
props: TextInputProps & React.InputHTMLAttributes<HTMLInputElement>
): React.ReactElement => {
export type OptionalTextInputProps = CustomTextInputProps &
JSX.IntrinsicElements['input']

type TextInputProps = RequiredTextInputProps & OptionalTextInputProps

export const TextInput = (props: TextInputProps): React.ReactElement => {
const {
id,
name,
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/Textarea/Textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface TextareaProps {
}

export const Textarea = (
props: TextareaProps & React.TextareaHTMLAttributes<HTMLTextAreaElement>
props: TextareaProps & JSX.IntrinsicElements['textarea']
): React.ReactElement => {
const {
id,
Expand Down
2 changes: 1 addition & 1 deletion src/components/header/ExtendedNav/ExtendedNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type ExtendedNavProps = {
}

export const ExtendedNav = (
props: ExtendedNavProps & React.HTMLAttributes<HTMLElement>
props: ExtendedNavProps & JSX.IntrinsicElements['nav']
): React.ReactElement => {
const {
primaryItems,
Expand Down
2 changes: 1 addition & 1 deletion src/components/header/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface HeaderProps {
}

export const Header = (
props: HeaderProps & React.HtmlHTMLAttributes<HTMLElement>
props: HeaderProps & JSX.IntrinsicElements['header']
): React.ReactElement => {
const {
basic,
Expand Down
8 changes: 4 additions & 4 deletions src/components/header/MegaMenu/MegaMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from 'react'

import { NavList } from '../NavList/NavList'
import { NavList, NavListProps } from '../NavList/NavList'

type MegaMenuProps = {
items: React.ReactNode[][]
isOpen: boolean
}

export const MegaMenu = (
props: MegaMenuProps & React.HTMLAttributes<HTMLUListElement>
props: MegaMenuProps & NavListProps
): React.ReactElement => {
const { items, isOpen, ...ulProps } = props
const { items, isOpen, ...navListProps } = props
return (
<div
className="usa-nav__submenu usa-megamenu"
Expand All @@ -19,7 +19,7 @@ export const MegaMenu = (
<div className="grid-row grid-gap-4">
{items.map((listItems, i) => (
<div className="usa-col" key={`subnav_col_${i}`}>
<NavList items={listItems} type="megamenu" {...ulProps} />
<NavList items={listItems} megamenu {...navListProps} />
</div>
))}
</div>
Expand Down
10 changes: 4 additions & 6 deletions src/components/header/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import React from 'react'
import { NavList } from '../NavList/NavList'
import { NavList, NavListProps } from '../NavList/NavList'

type MenuProps = {
items: React.ReactNode[]
isOpen: boolean
}

export const Menu = (
props: MenuProps & React.HTMLAttributes<HTMLUListElement>
): React.ReactElement => {
const { items, isOpen, ...listProps } = props
return <NavList items={items} type="subnav" hidden={!isOpen} {...listProps} />
export const Menu = (props: MenuProps & NavListProps): React.ReactElement => {
const { items, isOpen, ...navListProps } = props
return <NavList items={items} subnav hidden={!isOpen} {...navListProps} />
}

export default Menu
2 changes: 1 addition & 1 deletion src/components/header/NavCloseButton/NavCloseButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react'
import closeImg from 'uswds/src/img/close.svg'

export const NavCloseButton = (
props: React.HTMLAttributes<HTMLButtonElement>
props: JSX.IntrinsicElements['button']
): React.ReactElement => {
const { onClick, ...buttonProps } = props

Expand Down
Loading

0 comments on commit 2eba792

Please sign in to comment.