From 2eba7922ea2c6a4f4a710f989da41e7b847d4398 Mon Sep 17 00:00:00 2001 From: Andrew Hobson <21983+ahobson@users.noreply.github.com> Date: Tue, 30 Jun 2020 14:22:36 -0400 Subject: [PATCH] fix: Use JSX.ImplicitElements to derive valid property names (#267) * Use JSX.ImplicitElements to derive valid property names * Use name of attribute only when value is true --- docs/adding_new_components.md | 2 +- src/components/Button/Button.stories.tsx | 6 ++++++ src/components/Button/Button.test.tsx | 1 - src/components/Button/Button.tsx | 9 ++++----- src/components/Footer/SocialLinks/SocialLinks.tsx | 4 +--- src/components/GovBanner/GovBanner.tsx | 2 +- src/components/Search/Search.tsx | 4 ++-- src/components/Tag/Tag.tsx | 10 +++++++--- src/components/card/Card/Card.tsx | 2 +- src/components/card/CardBody/CardBody.tsx | 2 +- src/components/card/CardFooter/CardFooter.tsx | 2 +- src/components/card/CardGroup/CardGroup.tsx | 2 +- src/components/card/CardHeader/CardHeader.tsx | 2 +- src/components/card/CardMedia/CardMedia.tsx | 2 +- src/components/forms/Checkbox/Checkbox.tsx | 2 +- src/components/forms/DateInput/DateInput.tsx | 4 ++-- .../forms/DateInputGroup/DateInputGroup.tsx | 2 +- src/components/forms/Dropdown/Dropdown.tsx | 2 +- src/components/forms/Form/Form.tsx | 13 +++++++++---- src/components/forms/Radio/Radio.tsx | 2 +- src/components/forms/RangeInput/RangeInput.tsx | 2 +- src/components/forms/TextInput/TextInput.tsx | 14 ++++++++++---- src/components/forms/Textarea/Textarea.tsx | 2 +- src/components/header/ExtendedNav/ExtendedNav.tsx | 2 +- src/components/header/Header/Header.tsx | 2 +- src/components/header/MegaMenu/MegaMenu.tsx | 8 ++++---- src/components/header/Menu/Menu.tsx | 10 ++++------ .../header/NavCloseButton/NavCloseButton.tsx | 2 +- .../header/NavDropDownButton/NavDropDownButton.tsx | 2 +- src/components/header/NavList/NavList.tsx | 8 ++++---- .../header/NavMenuButton/NavMenuButton.tsx | 2 +- src/components/header/PrimaryNav/PrimaryNav.tsx | 2 +- src/components/header/Title/Title.tsx | 2 +- 33 files changed, 74 insertions(+), 59 deletions(-) diff --git a/docs/adding_new_components.md b/docs/adding_new_components.md index eeeaf44c97..a5f7d6d6a8 100644 --- a/docs/adding_new_components.md +++ b/docs/adding_new_components.md @@ -39,7 +39,7 @@ Pay special attention to: } export const Form = ( - props: FormProps & React.FormHTMLAttributes + props: FormProps & JSX.IntrinsicElements['form'] ): React.ReactElement => ``` diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index e8361a482c..9724938471 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -75,3 +75,9 @@ export const customClass = (): React.ReactElement => ( Click Me ) + +export const disabled = (): React.ReactElement => ( + +) diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx index 3898fd15b5..264a7dd447 100644 --- a/src/components/Button/Button.test.tsx +++ b/src/components/Button/Button.test.tsx @@ -99,7 +99,6 @@ describe('Button component', () => { ) fireEvent.click(getByText('Click Me')) - expect(onClickFn).toHaveBeenCalledTimes(1) }) diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index a299a57f37..4533297c2e 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -4,7 +4,6 @@ import { deprecationWarning } from '../../deprecation' interface ButtonProps { type: 'button' | 'submit' | 'reset' - disabled?: boolean children: React.ReactNode secondary?: boolean base?: boolean @@ -25,11 +24,10 @@ interface ButtonProps { } export const Button = ( - props: ButtonProps & React.HTMLAttributes + props: ButtonProps & JSX.IntrinsicElements['button'] ): React.ReactElement => { const { type, - disabled, children, secondary, base, @@ -43,6 +41,7 @@ export const Button = ( unstyled, onClick, className, + ...defaultProps } = props if (big) { @@ -75,9 +74,9 @@ export const Button = ( ) diff --git a/src/components/Footer/SocialLinks/SocialLinks.tsx b/src/components/Footer/SocialLinks/SocialLinks.tsx index 93608b9677..557563a0ee 100644 --- a/src/components/Footer/SocialLinks/SocialLinks.tsx +++ b/src/components/Footer/SocialLinks/SocialLinks.tsx @@ -5,9 +5,7 @@ type SocialLinksProps = { links: React.ReactNode[] } -export const SocialLinks = ( - props: SocialLinksProps & React.HTMLAttributes -): React.ReactElement => { +export const SocialLinks = (props: SocialLinksProps): React.ReactElement => { const { links } = props return ( diff --git a/src/components/GovBanner/GovBanner.tsx b/src/components/GovBanner/GovBanner.tsx index b16f8fcfe5..225899643d 100644 --- a/src/components/GovBanner/GovBanner.tsx +++ b/src/components/GovBanner/GovBanner.tsx @@ -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 + props: JSX.IntrinsicElements['section'] ): React.ReactElement => { const { className, ...sectionProps } = props const [isOpen, setOpenState] = useState(false) diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx index e8bdf7b6d3..a91f8abd40 100644 --- a/src/components/Search/Search.tsx +++ b/src/components/Search/Search.tsx @@ -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' @@ -25,7 +25,7 @@ interface SearchInputProps { } export const Search = ( - props: SearchInputProps & React.FormHTMLAttributes + props: SearchInputProps & OptionalFormProps ): React.ReactElement => { const { onSubmit, diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx index c3863398f5..43a4b65a87 100644 --- a/src/components/Tag/Tag.tsx +++ b/src/components/Tag/Tag.tsx @@ -7,9 +7,9 @@ interface TagProps { } export const Tag = ( - props: TagProps & React.HTMLAttributes + props: TagProps & JSX.IntrinsicElements['span'] ): React.ReactElement => { - const { children, background, className } = props + const { children, background, className, ...spanProps } = props const style: React.CSSProperties = {} if (background) { @@ -19,7 +19,11 @@ export const Tag = ( const tagClasses = classnames('usa-tag', className) return ( - + {children} ) diff --git a/src/components/card/Card/Card.tsx b/src/components/card/Card/Card.tsx index 41082746e0..1c03bc2844 100644 --- a/src/components/card/Card/Card.tsx +++ b/src/components/card/Card/Card.tsx @@ -10,7 +10,7 @@ interface CardProps { } export const Card = ( - props: CardProps & React.HTMLAttributes & GridLayoutProp + props: CardProps & JSX.IntrinsicElements['li'] & GridLayoutProp ): React.ReactElement => { const { layout = 'standardDefault', diff --git a/src/components/card/CardBody/CardBody.tsx b/src/components/card/CardBody/CardBody.tsx index 0ed45ef1de..a7582307f1 100644 --- a/src/components/card/CardBody/CardBody.tsx +++ b/src/components/card/CardBody/CardBody.tsx @@ -2,7 +2,7 @@ import React from 'react' import classnames from 'classnames' export const CardBody = ( - props: { exdent?: boolean } & React.HTMLAttributes + props: { exdent?: boolean } & JSX.IntrinsicElements['div'] ): React.ReactElement => { const { exdent, children, className, ...bodyProps } = props diff --git a/src/components/card/CardFooter/CardFooter.tsx b/src/components/card/CardFooter/CardFooter.tsx index 62f2a51ecf..bdaed80c40 100644 --- a/src/components/card/CardFooter/CardFooter.tsx +++ b/src/components/card/CardFooter/CardFooter.tsx @@ -2,7 +2,7 @@ import React from 'react' import classnames from 'classnames' export const CardFooter = ( - props: { exdent?: boolean } & React.HTMLAttributes + props: { exdent?: boolean } & JSX.IntrinsicElements['div'] ): React.ReactElement => { const { exdent, children, className, ...footerProps } = props diff --git a/src/components/card/CardGroup/CardGroup.tsx b/src/components/card/CardGroup/CardGroup.tsx index 6b2e42f4e0..b5fb3ebeab 100644 --- a/src/components/card/CardGroup/CardGroup.tsx +++ b/src/components/card/CardGroup/CardGroup.tsx @@ -2,7 +2,7 @@ import React from 'react' import classnames from 'classnames' export const CardGroup = ( - props: React.HTMLAttributes + props: JSX.IntrinsicElements['ul'] ): React.ReactElement => { const { children, className, ...ulProps } = props diff --git a/src/components/card/CardHeader/CardHeader.tsx b/src/components/card/CardHeader/CardHeader.tsx index ebdc4c3d88..9704bd00b6 100644 --- a/src/components/card/CardHeader/CardHeader.tsx +++ b/src/components/card/CardHeader/CardHeader.tsx @@ -2,7 +2,7 @@ import React from 'react' import classnames from 'classnames' export const CardHeader = ( - props: { exdent?: boolean } & React.HTMLAttributes + props: { exdent?: boolean } & JSX.IntrinsicElements['header'] ): React.ReactElement => { const { exdent, children, className, ...headerProps } = props diff --git a/src/components/card/CardMedia/CardMedia.tsx b/src/components/card/CardMedia/CardMedia.tsx index 2c3542c2a6..5176da2677 100644 --- a/src/components/card/CardMedia/CardMedia.tsx +++ b/src/components/card/CardMedia/CardMedia.tsx @@ -9,7 +9,7 @@ interface CardMediaProps { } export const CardMedia = ( - props: CardMediaProps & React.HTMLAttributes + props: CardMediaProps & JSX.IntrinsicElements['div'] ): React.ReactElement => { const { exdent, diff --git a/src/components/forms/Checkbox/Checkbox.tsx b/src/components/forms/Checkbox/Checkbox.tsx index 23edf00ecf..8b7881f28b 100644 --- a/src/components/forms/Checkbox/Checkbox.tsx +++ b/src/components/forms/Checkbox/Checkbox.tsx @@ -15,7 +15,7 @@ interface CheckboxProps { } export const Checkbox = ( - props: CheckboxProps & React.InputHTMLAttributes + props: CheckboxProps & JSX.IntrinsicElements['input'] ): React.ReactElement => { const { id, name, className, label, inputRef, ...inputProps } = props diff --git a/src/components/forms/DateInput/DateInput.tsx b/src/components/forms/DateInput/DateInput.tsx index 18df6ad43d..3a6b61e030 100644 --- a/src/components/forms/DateInput/DateInput.tsx +++ b/src/components/forms/DateInput/DateInput.tsx @@ -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' @@ -15,7 +15,7 @@ interface DateInputElementProps { } export const DateInput = ( - props: DateInputElementProps & React.InputHTMLAttributes + props: DateInputElementProps & OptionalTextInputProps ): React.ReactElement => { const { id, diff --git a/src/components/forms/DateInputGroup/DateInputGroup.tsx b/src/components/forms/DateInputGroup/DateInputGroup.tsx index d133cc1433..3623f7e341 100644 --- a/src/components/forms/DateInputGroup/DateInputGroup.tsx +++ b/src/components/forms/DateInputGroup/DateInputGroup.tsx @@ -2,7 +2,7 @@ import React from 'react' import classnames from 'classnames' export const DateInputGroup = ( - props: React.HTMLAttributes + props: JSX.IntrinsicElements['div'] ): React.ReactElement => { const { children, className, ...divAttributes } = props diff --git a/src/components/forms/Dropdown/Dropdown.tsx b/src/components/forms/Dropdown/Dropdown.tsx index c84598ec10..67826dd7b2 100644 --- a/src/components/forms/Dropdown/Dropdown.tsx +++ b/src/components/forms/Dropdown/Dropdown.tsx @@ -15,7 +15,7 @@ interface DropdownProps { } export const Dropdown = ( - props: DropdownProps & React.SelectHTMLAttributes + props: DropdownProps & JSX.IntrinsicElements['select'] ): React.ReactElement => { const { id, name, className, inputRef, children, ...inputProps } = props diff --git a/src/components/forms/Form/Form.tsx b/src/components/forms/Form/Form.tsx index c7dc9265c6..5ba54aa44b 100644 --- a/src/components/forms/Form/Form.tsx +++ b/src/components/forms/Form/Form.tsx @@ -1,17 +1,22 @@ import React from 'react' import classnames from 'classnames' -interface FormProps { +interface RequiredFormProps { children: React.ReactNode onSubmit: (event: React.FormEvent) => void +} + +interface CustomFormProps { className?: string large?: boolean search?: boolean } -export const Form = ( - props: FormProps & React.FormHTMLAttributes -): 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( diff --git a/src/components/forms/Radio/Radio.tsx b/src/components/forms/Radio/Radio.tsx index 0bad84f6eb..302f6a16d7 100644 --- a/src/components/forms/Radio/Radio.tsx +++ b/src/components/forms/Radio/Radio.tsx @@ -15,7 +15,7 @@ interface RadioProps { } export const Radio = ( - props: RadioProps & React.InputHTMLAttributes + props: RadioProps & JSX.IntrinsicElements['input'] ): React.ReactElement => { const { id, name, className, label, inputRef, ...inputProps } = props diff --git a/src/components/forms/RangeInput/RangeInput.tsx b/src/components/forms/RangeInput/RangeInput.tsx index 9cc105cdcd..40efe1bac3 100644 --- a/src/components/forms/RangeInput/RangeInput.tsx +++ b/src/components/forms/RangeInput/RangeInput.tsx @@ -13,7 +13,7 @@ interface RangeInputProps { } export const RangeInput = ( - props: RangeInputProps & React.InputHTMLAttributes + 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 diff --git a/src/components/forms/TextInput/TextInput.tsx b/src/components/forms/TextInput/TextInput.tsx index 110e1c82be..0296c7099e 100644 --- a/src/components/forms/TextInput/TextInput.tsx +++ b/src/components/forms/TextInput/TextInput.tsx @@ -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' /** @@ -33,9 +36,12 @@ interface TextInputProps { | undefined } -export const TextInput = ( - props: TextInputProps & React.InputHTMLAttributes -): React.ReactElement => { +export type OptionalTextInputProps = CustomTextInputProps & + JSX.IntrinsicElements['input'] + +type TextInputProps = RequiredTextInputProps & OptionalTextInputProps + +export const TextInput = (props: TextInputProps): React.ReactElement => { const { id, name, diff --git a/src/components/forms/Textarea/Textarea.tsx b/src/components/forms/Textarea/Textarea.tsx index 94685e1717..b31e0b5fb7 100644 --- a/src/components/forms/Textarea/Textarea.tsx +++ b/src/components/forms/Textarea/Textarea.tsx @@ -17,7 +17,7 @@ interface TextareaProps { } export const Textarea = ( - props: TextareaProps & React.TextareaHTMLAttributes + props: TextareaProps & JSX.IntrinsicElements['textarea'] ): React.ReactElement => { const { id, diff --git a/src/components/header/ExtendedNav/ExtendedNav.tsx b/src/components/header/ExtendedNav/ExtendedNav.tsx index cc9b5816f8..b325c1dc8f 100644 --- a/src/components/header/ExtendedNav/ExtendedNav.tsx +++ b/src/components/header/ExtendedNav/ExtendedNav.tsx @@ -14,7 +14,7 @@ type ExtendedNavProps = { } export const ExtendedNav = ( - props: ExtendedNavProps & React.HTMLAttributes + props: ExtendedNavProps & JSX.IntrinsicElements['nav'] ): React.ReactElement => { const { primaryItems, diff --git a/src/components/header/Header/Header.tsx b/src/components/header/Header/Header.tsx index 4c81bb90ca..70e94cb76b 100644 --- a/src/components/header/Header/Header.tsx +++ b/src/components/header/Header/Header.tsx @@ -9,7 +9,7 @@ interface HeaderProps { } export const Header = ( - props: HeaderProps & React.HtmlHTMLAttributes + props: HeaderProps & JSX.IntrinsicElements['header'] ): React.ReactElement => { const { basic, diff --git a/src/components/header/MegaMenu/MegaMenu.tsx b/src/components/header/MegaMenu/MegaMenu.tsx index bd32a4e626..0f98db23fd 100644 --- a/src/components/header/MegaMenu/MegaMenu.tsx +++ b/src/components/header/MegaMenu/MegaMenu.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { NavList } from '../NavList/NavList' +import { NavList, NavListProps } from '../NavList/NavList' type MegaMenuProps = { items: React.ReactNode[][] @@ -8,9 +8,9 @@ type MegaMenuProps = { } export const MegaMenu = ( - props: MegaMenuProps & React.HTMLAttributes + props: MegaMenuProps & NavListProps ): React.ReactElement => { - const { items, isOpen, ...ulProps } = props + const { items, isOpen, ...navListProps } = props return (
{items.map((listItems, i) => (
- +
))}
diff --git a/src/components/header/Menu/Menu.tsx b/src/components/header/Menu/Menu.tsx index ecc9743279..1841222697 100644 --- a/src/components/header/Menu/Menu.tsx +++ b/src/components/header/Menu/Menu.tsx @@ -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 -): React.ReactElement => { - const { items, isOpen, ...listProps } = props - return