web/src/components/KebabMenu.tsx
import React, { ReactElement, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { CloseIcon, MenuIcon } from '../assets'
import dimensions from '../constants/dimensions'
import useLockedBody from '../hooks/useLockedBody'
import '../styles/KebabMenu.css'
import Portal from './Portal'
import Button from './base/Button'
import Icon from './base/Icon'
type KebabMenuProps = {
items: Array<ReactNode>
show: boolean
setShow: (show: boolean) => void
Footer: ReactNode
}
const ToggleContainer = styled.div`
display: flex;
padding: 0 8px;
z-index: 50;
`
const List = styled.div<{ $show: boolean }>`
font-family: ${props => props.theme.fonts.web.decorativeFont};
position: absolute;
top: 0;
width: 80vw;
height: 100vh;
background-color: ${props => props.theme.colors.backgroundColor};
box-shadow: -3px 3px 3px 0 rgb(0 0 0 / 13%);
/* to stop flickering of text in safari */
-webkit-font-smoothing: antialiased;
transform-origin: 0 0;
transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1);
z-index: 40;
${props => (props.theme.contentDirection === 'rtl' ? `left: 0;` : `right:0;`)}
${props =>
props.theme.contentDirection === 'rtl' ? `transform: translate(-100%, 0);` : `transform: translate(100%, 0);`}
${props => props.$show && `opacity: 1;transform: none;`}
display: flex;
flex-direction: column;
`
const Overlay = styled.div<{ $show: boolean }>`
position: absolute;
width: 100%;
height: 100vh;
top: 0;
inset-inline-start: 0;
background-color: rgb(0 0 0 / 50%);
z-index: 30;
display: ${props => (props.$show ? `block` : `none`)};
`
const Heading = styled.div`
display: flex;
justify-content: ${props => (props.theme.contentDirection === 'rtl' ? `flex-start` : `flex-end`)};
background-color: ${props => props.theme.colors.backgroundAccentColor};
box-shadow: -3px 3px 3px 0 rgb(0 0 0 / 13%);
height: ${dimensions.headerHeightSmall}px;
padding: 0 8px;
`
const Content = styled.div`
padding: 0 32px;
`
const StyledIcon = styled(Icon)`
width: 28px;
height: 28px;
`
const KebabMenu = ({ items, show, setShow, Footer }: KebabMenuProps): ReactElement | null => {
useLockedBody(show)
const { t } = useTranslation('layout')
const onClick = () => {
setShow(!show)
}
if (items.length === 0) {
return null
}
return (
<ToggleContainer>
<Button onClick={onClick} label={t('sideBarOpenAriaLabel')} aria-expanded={show}>
<StyledIcon src={MenuIcon} />
</Button>
<Portal
className='kebab-menu'
show={show}
style={{
visibility: show ? 'visible' : 'hidden',
top: window.scrollY > 0 ? `${window.scrollY}px` : undefined,
}}>
{/* disabled because this is an overlay for backdrop close */}
{/* eslint-disable-next-line styled-components-a11y/no-static-element-interactions,styled-components-a11y/click-events-have-key-events */}
<Overlay onClick={onClick} $show={show} />
<List $show={show}>
<Heading>
<Button onClick={onClick} label={t('sideBarCloseAriaLabel')}>
<Icon src={CloseIcon} />
</Button>
</Heading>
<Content>{items}</Content>
{Footer}
</List>
</Portal>
</ToggleContainer>
)
}
export default KebabMenu