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

♻️ Popover story to Typescript #797

Merged
merged 10 commits into from
Oct 30, 2020
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,64 @@ const TextWrapper = styled.div`
export default {
title: 'Components/Popover',
component: Popover,
decorators: [withKnobs],
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 +268,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 +339,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
6 changes: 2 additions & 4 deletions libraries/core-react/src/Popover/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { Popover as BaseComponent } from './Popover'
import { Popover as BaseComponent, PopoverProps as Props } from './Popover'
import { PopoverTitle } from './PopoverTitle'
import { PopoverAnchor } from './PopoverAnchor'
import { PopoverContent } from './PopoverContent'
import { PopoverItem } from './PopoverItem'

type PopoverTypes = typeof BaseComponent & {
pomfrida marked this conversation as resolved.
Show resolved Hide resolved
PopoverTitle: typeof PopoverTitle
PopoverAnchor: typeof PopoverAnchor
PopoverContent: typeof PopoverContent
PopoverItem: typeof PopoverItem
}

const Popover = BaseComponent as PopoverTypes

Popover.PopoverTitle = PopoverTitle
Popover.PopoverAnchor = PopoverAnchor
Popover.PopoverContent = PopoverContent
Popover.PopoverItem = PopoverItem

export { Popover }
export type PopoverProps = Props
Loading