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

Migrate Details component to TSX #1026

Merged
merged 9 commits into from
Feb 11, 2021
5 changes: 5 additions & 0 deletions .changeset/hip-ads-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/components": patch
---

Migrate `Details` to TypeScript
8 changes: 5 additions & 3 deletions src/Details.js → src/Details.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import styled from 'styled-components'
import {COMMON} from './constants'
import theme from './theme'
import sx from './sx'
import {COMMON, SystemCommonProps} from './constants'
import {ComponentProps} from './utils/types'
import sx, {SxProp} from './sx'

const Details = styled.details`
const Details = styled.details<SystemCommonProps & SxProp>`
& > summary {
list-style: none;
}
Expand All @@ -26,4 +27,5 @@ Details.propTypes = {
...sx.propTypes
}

export type DetailsProps = ComponentProps<typeof Details>
export default Details
5 changes: 3 additions & 2 deletions src/__tests__/Details.js → src/__tests__/Details.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import {Details, useDetails, Button, ButtonPrimary, Box} from '..'
import {ButtonProps} from '../Button/Button'
import {mount, behavesAsComponent, checkExports} from '../utils/testing'
import {COMMON} from '../constants'
import {render as HTMLRender, cleanup} from '@testing-library/react'
Expand Down Expand Up @@ -66,7 +67,7 @@ describe('Details', () => {
})

it('Can manipulate state with setOpen', () => {
const CloseButton = props => <Button {...props} />
const CloseButton = (props: ButtonProps) => <Button {...props} />
const Component = () => {
const {getDetailsProps, setOpen, open} = useDetails({closeOnOutsideClick: true, defaultOpen: true})
return (
Expand All @@ -92,7 +93,7 @@ describe('Details', () => {

it('Does not toggle when you click inside', () => {
const Component = () => {
const {getDetailsProps} = useDetails({closeOnOutsideClick: true, defaultOpen: true})
const {getDetailsProps, open} = useDetails({closeOnOutsideClick: true, defaultOpen: true})
return (
<Details {...getDetailsProps}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. It looks like none of the Details tests are using the getDetailsProps function correctly. They all need to updated to call the function and pass the return value to the Details component:

Suggested change
<Details {...getDetailsProps}>
<Details {...getDetailsProps()}>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll also probably need to update the prop type of Details to accept the return value of getDetailsProps

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to accomplish this in 57dafdd and I'm getting a TS error. About to sign off here, but I might sync up with you in the morning to see if we can take a look at that.

<Button as="summary">{open ? 'Open' : 'Closed'}</Button>
Expand Down
21 changes: 16 additions & 5 deletions src/hooks/useDetails.js → src/hooks/useDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import {useCallback, useEffect, useState, useRef} from 'react'

function useDetails({ref, closeOnOutsideClick, defaultOpen, onClickOutside} = {}) {
type useDetailsParameters = {
VanAnderson marked this conversation as resolved.
Show resolved Hide resolved
ref?: {current: HTMLElement}
VanAnderson marked this conversation as resolved.
Show resolved Hide resolved
closeOnOutsideClick?: boolean
defaultOpen?: boolean
onClickOutside?: Function
VanAnderson marked this conversation as resolved.
Show resolved Hide resolved
}

function useDetails({ref, closeOnOutsideClick, defaultOpen, onClickOutside}: useDetailsParameters) {
const [open, setOpen] = useState(defaultOpen)
const backupRef = useRef(null)
const customRef = ref ?? backupRef

const onClickOutsideInternal = useCallback(
event => {
if (event.target.closest('details') !== customRef.current) {
(event: MouseEvent) => {
const {current} = customRef
const eventTarget = event.target as HTMLElement
const closest = eventTarget.closest('details') as HTMLDetailsElement
if (closest !== current) {
onClickOutside && onClickOutside(event)
if (!event.defaultPrevented) {
setOpen(false)
Expand All @@ -27,9 +37,10 @@ function useDetails({ref, closeOnOutsideClick, defaultOpen, onClickOutside} = {}
}
}, [open, closeOnOutsideClick, onClickOutsideInternal])

const handleToggle = e => {
const handleToggle = (e: Event) => {
if (!e.defaultPrevented) {
setOpen(e.target.open)
const eventTarget = e.target as HTMLDetailsElement
setOpen(eventTarget.open)
}
}

Expand Down