Skip to content

Commit

Permalink
♻️ Popover story to Typescript (#797)
Browse files Browse the repository at this point in the history
* ♻️ JSX to TSX

* ♻️ Popover default story with controls

* 🔥 PopoverItem removed from export

PopoverItem is just for internal use, not needed elsewhere

* ♻️ Rename function

* 🔥 Removed comments

Want to fix these errors instead

* ♻️ Type React.SyntheticEvent (generic for events) on event handlers

Cleared lint warnings

* 🚨 Fixed linter warnings in story

* ♻️ Open Popover via control AND anchor (story)

* ♻️ Props name changed in test

* ♻️ Export merged types
  • Loading branch information
pomfrida authored Oct 30, 2020
1 parent 01aef05 commit ba37dfe
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 158 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import React from 'react'
import { withKnobs, select, text } from '@storybook/addon-knobs'
import styled from 'styled-components'
import {
Typography,
Button,
Avatar,
Chip,
TextField,
Search,
Icon,
Popover,
PopoverProps,
Card,
} from '@equinor/eds-core-react'
import catImg from '../images/cat.jpg'
import { Meta, Story } from '@storybook/react'

const { PopoverAnchor, PopoverTitle, PopoverContent } = Popover
const { CardActions } = Card
Expand All @@ -36,18 +31,70 @@ const TextWrapper = styled.div`
export default {
title: 'Components/Popover',
component: Popover,
decorators: [withKnobs],
subcomponents: {
PopoverAnchor,
PopoverTitle,
PopoverContent,
CardActions,
},
argTypes: {
placement: {
control: {
type: 'select',
options: [
'topLeft',
'top',
'topRight',
'rightTop',
'right',
'rightBottom',
'bottomLeft',
'bottom',
'bottomRight',
'leftTop',
'left',
'leftBottom',
],
},
},
},
} as Meta

export const Default: Story<PopoverProps> = (args) => {
const [active, setActive] = React.useState(false)
const handleToggle = () => {
setActive(!active)
}
return (
<div style={{ margin: '10em' }}>
<Popover {...args} onClose={handleToggle} open={active || args.open}>
<PopoverAnchor>
<Button id="1" onClick={handleToggle}>
Click me!
</Button>
</PopoverAnchor>
<PopoverTitle>Title</PopoverTitle>
<PopoverContent>
<Typography variant="body_short">Content</Typography>
</PopoverContent>
<CardActions>
<Button onClick={handleToggle}>Cancel</Button>
<Button onClick={handleToggle}>OK</Button>
</CardActions>
</Popover>
</div>
)
}

export function Placement() {
const [active, setActive] = React.useState(null)
export const Placements: Story<PopoverProps> = () => {
const [active, setActive] = React.useState('')

const handleClick = (event) => {
const handleClick = (event: React.SyntheticEvent) => {
setActive(event.currentTarget.id)
}

const handleClose = () => {
setActive(null)
setActive('')
}

const Content = () => (
Expand Down Expand Up @@ -227,22 +274,22 @@ export function Placement() {
)
}

export function ActivationTypes() {
const [active, setActive] = React.useState(null)
export const ActivationTypes: Story<PopoverProps> = () => {
const [active, setActive] = React.useState('')

const handleClick = (event) => {
const handleClick = (event: React.SyntheticEvent) => {
setActive(event.currentTarget.id)
}

const handleHover = (event) => {
const handleHover = (event: React.SyntheticEvent) => {
const current = event.currentTarget.id
setTimeout(() => {
setActive(current)
}, 300)
}

const handleClose = () => {
setActive(null)
setActive('')
}

const Content = () => (
Expand Down Expand Up @@ -298,86 +345,3 @@ export function ActivationTypes() {
</Body>
)
}

const ANCHOR_CHOICES = {
button: <Button variant="ghost">Button</Button>,
avatar: <Avatar src={catImg} size={48} alt="avatar" />,
chip: <Chip>Chip</Chip>,
search: (
<Search aria-label="sitewide" id="search-normal" placeholder="Search" />
),
textfield: (
<TextField
id="textfield-normal"
placeholder="Placeholder text"
label="Text"
helperText="Helper text"
/>
),
typography: <Typography variant="h3">Typography</Typography>,
icon: <Icon name="work" color={'red'} />,
}

const ACTIONS_CHOICES = {
none: '',
default: (
<CardActions>
<Button>Cancel</Button>
<Button>OK</Button>
</CardActions>
),
alignRight: (
<CardActions alignRight>
<Button>Cancel</Button>
<Button>OK</Button>
</CardActions>
),
meta: (
<CardActions meta="Share">
<Button>OK</Button>
</CardActions>
),
}

export const WithKnobs = () => {
const anchor = select('Anchor', Object.keys(ANCHOR_CHOICES), 'avatar')
const title = text('Title', 'Title')
const content = text('Content', 'Content')
const action = select('Actions', Object.keys(ACTIONS_CHOICES), 'default')
const placement = select(
'Placement',
[
'topLeft',
'top',
'topRight',
'rightTop',
'right',
'rightBottom',
'bottomLeft',
'bottom',
'bottomRight',
'leftTop',
'left',
'leftBottom',
],
'bottom',
)

return (
<Body>
<TextWrapper>
<Typography variant="h3">With knobs</Typography>
</TextWrapper>
<Wrapper>
<Popover placement={placement} open>
<PopoverTitle>{title}</PopoverTitle>
<PopoverAnchor>{ANCHOR_CHOICES[anchor]}</PopoverAnchor>
<PopoverContent>
<Typography variant="body_short">{content}</Typography>
</PopoverContent>
{ACTIONS_CHOICES[action]}
</Popover>
</Wrapper>
</Body>
)
}
9 changes: 6 additions & 3 deletions libraries/core-react/src/Popover/Popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { popover as tokens } from './Popover.tokens'
import { Popover } from '.'
import { Button } from '../Button'
import { Typography } from '../Typography'
import type { Props } from './Popover'
import type { PopoverProps } from './Popover'

const { PopoverTitle, PopoverContent, PopoverAnchor } = Popover

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

afterEach(cleanup)

const SimplePopover = ({ open = false, placement = 'bottom' }: Props) => (
const SimplePopover = ({
open = false,
placement = 'bottom',
}: PopoverProps) => (
<Popover open={open} placement={placement}>
<PopoverAnchor>
<Button onClick={(e) => e.stopPropagation()}>On Click</Button>
Expand Down Expand Up @@ -61,7 +64,7 @@ describe('Popover', () => {
expect(arrow).toHaveStyleRule('right', `${topRight.arrowRight}`)
expect(arrow).toHaveStyleRule('bottom', `${topRight.arrowBottom}`)
})
it('Has provided necessary props', () => {
it('Has provided necessary PopoverProps', () => {
const placement = 'topRight'
const { queryByText } = render(<SimplePopover open placement={placement} />)
expect(queryByText(placement)).toBeDefined()
Expand Down
89 changes: 45 additions & 44 deletions libraries/core-react/src/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ type PopoverSplit = {
childArray: ReactNode[]
}

export type Props = {
/* Popover placement relative to anchor */
export type PopoverProps = {
/** Popover placement relative to anchor */
placement?:
| 'topLeft'
| 'top'
Expand All @@ -42,58 +42,59 @@ export type Props = {
| 'leftBottom'
/** On Close function */
onClose?: () => void
/** Open activates <PopoverItem/> */
/** Open activates Popover */
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)
export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
function Popover({ open, children, placement = 'bottom', ...rest }, ref) {
const props = {
...rest,
placement,
ref,
}
if (!children) {
return <Container {...props} />
}
const anchorRef = useRef<HTMLDivElement>(null)

const { anchorElement, childArray } = React.Children.toArray(children).reduce(
(acc: PopoverSplit, child): PopoverSplit => {
if (isValidElement(child) && child.type === PopoverAnchor) {
const { anchorElement, childArray } = React.Children.toArray(
children,
).reduce(
(acc: PopoverSplit, child): PopoverSplit => {
if (isValidElement(child) && child.type === PopoverAnchor) {
return {
...acc,
anchorElement: child,
}
}
return {
...acc,
anchorElement: child,
childArray: [...acc.childArray, child],
}
}
return {
...acc,
childArray: [...acc.childArray, child],
}
},
{ anchorElement: null, childArray: [] },
)
},
{ anchorElement: null, childArray: [] },
)

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

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

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

// Popover.displayName = 'eds-popover'
8 changes: 4 additions & 4 deletions libraries/core-react/src/Popover/PopoverItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const StyledCloseButton = styled(Button)`
}
`

type Props = {
type PopoverItemProps = {
/* Popover placement relative to anchor */
placement?:
| 'topLeft'
Expand All @@ -98,12 +98,12 @@ type Props = {
| 'leftBottom'
/** On Close function */
onClose?: () => void
/** Open activates <PopoverItem/> */
/** Anchor reference */
anchorRef: React.MutableRefObject<HTMLDivElement>
} & HTMLAttributes<HTMLDivElement>

export const PopoverItem = forwardRef<HTMLDivElement, Props>(
function EdsPopoverItem(
export const PopoverItem = forwardRef<HTMLDivElement, PopoverItemProps>(
function PopoverItem(
{
children,
onClose = () => {},
Expand Down
Loading

0 comments on commit ba37dfe

Please sign in to comment.