Skip to content

Commit

Permalink
Typescript for popovers (#695)
Browse files Browse the repository at this point in the history
* 🚚 Rename to typescript extension

* 🏷️ Some more types and typescript support

* 🏷️ Add types for anchor, content and title

* ♻️ Fix imports for test

* ♻️ DRY and placement token

* 🎨 Better readability

* 🐛 Add clode-icon explicit

* ♻️ Another approach for popover children

* Remove empty line

* Add icon with data attr

* Make @mimarz happy by extending the component

* Add support for missing children as React's native functionality
  • Loading branch information
wenche authored and vnys committed Nov 13, 2020
1 parent b23187a commit a739189
Show file tree
Hide file tree
Showing 13 changed files with 474 additions and 395 deletions.
108 changes: 0 additions & 108 deletions libraries/core-react/src/Popover/Popover.jsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import 'jest-styled-components'
import styled from 'styled-components'
import { popover as tokens } from './Popover.tokens'
import { Popover } from '.'
import { Button, Typography } from '..'
import { Button } from '../Button'
import { Typography } from '../Typography'
import type { Props } from './Popover'

const { PopoverTitle, PopoverContent, PopoverAnchor } = Popover

Expand All @@ -22,7 +24,7 @@ const {

afterEach(cleanup)

const SimplePopover = ({ open, placement }) => (
const SimplePopover = ({ open = false, placement = 'bottom' }: Props) => (
<Popover open={open} placement={placement}>
<PopoverAnchor>
<Button onClick={(e) => e.stopPropagation()}>On Click</Button>
Expand Down Expand Up @@ -81,3 +83,45 @@ describe('Popover', () => {
expect(popover).toHaveStyleRule('width', '100px')
})
})
it("Doesn't crash if no children is provided to Popover component", () => {
const placement = 'topLeft'
const { queryByText } = render(<Popover placement={placement} />)
expect(queryByText(placement)).toBeDefined()
})
it("Doesn't crash if Popover anchor is missing content", () => {
const placement = 'topLeft'
const { queryByText } = render(
<Popover placement={placement}>
<PopoverAnchor />
<PopoverTitle>Title</PopoverTitle>
<PopoverContent>Content</PopoverContent>
</Popover>,
)
expect(queryByText(placement)).toBeDefined()
})
it("Doesn't crash if no children is provided to Popover content", () => {
const placement = 'topLeft'
const { queryByText } = render(
<Popover placement={placement}>
<PopoverAnchor>
<Button onClick={(e) => e.stopPropagation()}>On Click</Button>
</PopoverAnchor>
<PopoverTitle>Title</PopoverTitle>
<PopoverContent />
</Popover>,
)
expect(queryByText(placement)).toBeDefined()
})
it("Doesn't crash if title is missing", () => {
const placement = 'topLeft'
const { queryByText } = render(
<Popover placement={placement}>
<PopoverAnchor>
<Button onClick={(e) => e.stopPropagation()}>On Click</Button>
</PopoverAnchor>
<PopoverTitle />
<PopoverContent>Content</PopoverContent>
</Popover>,
)
expect(queryByText(placement)).toBeDefined()
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-nocheck
import { tokens } from '@equinor/eds-tokens'
import type { Typography, Spacing } from '@equinor/eds-tokens'

const {
typography: {
Expand All @@ -19,7 +19,52 @@ const {
elevation: { overlay: elevation },
} = tokens

export const popover = {
export type Placement = {
arrowLeft?: string
arrowTop?: string
arrowRight?: string
arrowBottom?: string
arrowTransform?: string
transform?: string
width?: string
popoverBottom?: string | number
popoverLeft?: string | number
popoverRight?: string | number
popoverTop?: string | number
}

type Popover = {
header: Typography
background: string
elevation: string
popover: {
minHeight: string
maxWidth: string
maxHeight: string
}
arrow: {
width: string
height: string
}
spacings: Spacing
borderRadius: string
placement: {
bottom: Placement
bottomRight: Placement
bottomLeft: Placement
top: Placement
topRight: Placement
topLeft: Placement
left: Placement
leftTop: Placement
leftBottom: Placement
right: Placement
rightTop: Placement
rightBottom: Placement
}
}

export const popover: Popover = {
header,
background,
elevation,
Expand Down
99 changes: 99 additions & 0 deletions libraries/core-react/src/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { forwardRef, useRef, HTMLAttributes, ReactNode } from 'react'
import styled from 'styled-components'
import { PopoverItem } from './PopoverItem'

const Container = styled.div`
position: relative;
display: inline-flex;
justify-content: center;
`

const Anchor = styled.div`
&:focus {
outline: none;
}
`
type PopoverChild = {
type?: { displayName?: string }
} & ReactNode

export type Props = {
/* Popover placement relative to anchor */
placement?:
| 'topLeft'
| 'top'
| 'topRight'
| 'rightTop'
| 'right'
| 'rightBottom'
| 'bottomLeft'
| 'bottom'
| 'bottomRight'
| 'leftTop'
| 'left'
| 'leftBottom'
/** On Close function */
onClose?: () => void
/** Open activates <PopoverItem/> */
open?: boolean
} & HTMLAttributes<HTMLDivElement>

// Controller Component for PopoverItem
export const Popover = forwardRef<HTMLDivElement, Props>(function Popover(
{ open, children, placement = 'bottom', ...rest },
ref,
) {
const props = {
...rest,
placement,
ref,
}
if (!children) {
return <Container {...props} />
}
const anchorRef = useRef<HTMLDivElement>(null)
const popoverChildren: PopoverChild | PopoverChild[] = children
let anchorElement: PopoverChild
const childArray = []
if (Array.isArray(popoverChildren)) {
for (let i = 0; i < popoverChildren.length; i += 1) {
/*
Find anchor element in children to wrap the element together with <PopoverItem/>.
Children is required, but user has to wrap the actual anchor with <PopoverAnchor />
*/
const child = popoverChildren[i] as PopoverChild
if (child.type && child.type.displayName === 'eds-popover-anchor') {
anchorElement = child
} else {
// Add the remaining children to a new array to display inside <PopoverItem/>
childArray.push(child)
}
}
} else if (
!Array.isArray(popoverChildren) &&
popoverChildren.type &&
popoverChildren.type.displayName === 'eds-popover-anchor'
) {
anchorElement = popoverChildren
}

if (open && anchorRef.current) {
anchorRef.current.focus()
}

return (
<Container {...props}>
<Anchor aria-haspopup="true" ref={anchorRef}>
{anchorElement}
</Anchor>

{open && (
<PopoverItem {...props} anchorRef={anchorRef}>
{childArray}
</PopoverItem>
)}
</Container>
)
})

Popover.displayName = 'eds-popover'
29 changes: 0 additions & 29 deletions libraries/core-react/src/Popover/PopoverAnchor.jsx

This file was deleted.

15 changes: 15 additions & 0 deletions libraries/core-react/src/Popover/PopoverAnchor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { forwardRef, HTMLAttributes } from 'react'

type Props = HTMLAttributes<HTMLDivElement>

export const PopoverAnchor = forwardRef<HTMLDivElement, Props>(
function PopoverAnchor({ children, ...rest }, ref) {
const props = {
...rest,
ref,
}
return <div {...props}>{children}</div>
},
)

PopoverAnchor.displayName = 'eds-popover-anchor'
Loading

0 comments on commit a739189

Please sign in to comment.