Skip to content

Commit

Permalink
Merge pull request #1427 from habx/feature/APP-15727
Browse files Browse the repository at this point in the history
APP-15727: Introduce the `TogglePanel` component
  • Loading branch information
habx-auto-merge[bot] authored Nov 5, 2020
2 parents 0c570ff + 907f99e commit 61aa246
Show file tree
Hide file tree
Showing 21 changed files with 318 additions and 204 deletions.
2 changes: 1 addition & 1 deletion src/AutocompleteInput/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { AutocompleteInput } from './AutocompleteInput'

export { AutocompleteInputProps } from './AutocompleteInput.interface'
export type { AutocompleteInputProps } from './AutocompleteInput.interface'
5 changes: 3 additions & 2 deletions src/Layout/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { Layout } from './Layout'

export { LayoutProps, LayoutChild } from './Layout.interface'
export { useParentLayout } from './Layout.context'
export { LayoutChild } from './Layout.interface'

export type { LayoutProps } from './Layout.interface'
17 changes: 4 additions & 13 deletions src/Menu/Menu.interface.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
import { Modal } from '@delangle/use-modal'
import * as React from 'react'

import { TogglePanelProps } from '../TogglePanel'
import { WithTriggerElement } from '../withTriggerElement'

export interface MenuInstance {
open: boolean
onClose: () => void
}

export interface MenuInnerProps
extends React.HTMLAttributes<HTMLUListElement>,
export interface InnerMenuProps
extends Omit<TogglePanelProps, 'setStyle' | keyof MenuInstance>,
MenuInstance {
fullScreenOnMobile?: boolean
scrollable?: boolean
position?: 'horizontal' | 'vertical'
triggerRef?: React.RefObject<HTMLElement>
withOverlay?: boolean
setPosition?: (dimensions: {
triggerDimensions: DOMRect
menuHeight: number
menuWidth: number
}) => { top?: number; left?: number; right?: number; bottom?: number }
children?:
| React.ReactNode
| ((modal: Modal<HTMLUListElement>) => React.ReactNode)
}

export interface MenuProps
extends WithTriggerElement<MenuInnerProps, HTMLUListElement> {}
export type MenuProps = WithTriggerElement<InnerMenuProps, HTMLDivElement>
48 changes: 5 additions & 43 deletions src/Menu/Menu.style.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import styled from 'styled-components'

import { zIndex } from '../_internal/zIndex'
import { animations } from '../animations'
import { theme } from '../theme'

export const MenuTriggerContainer = styled.span`
position: relative;
align-self: flex-start;
`

export const MenuContent = styled.div`
background-color: ${theme.color('background', { useRootTheme: true })};
export const FloatingMenu = styled.ul`
margin: 0;
padding: 8px 0;
background-color: ${theme.color('background', { useRootTheme: true })};
box-shadow: ${theme.shadow()};
border-radius: 4px;
Expand All @@ -22,40 +16,8 @@ export const MenuContent = styled.div`
}
`

export const MenuOverlay = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: ${zIndex.dropDowns};
`

export const MenuContainer = styled.ul`
opacity: 1;
list-style-type: none;
padding: 0;
margin: 0;
position: fixed;
z-index: ${zIndex.dropDowns};
&:not([data-state='opened']) {
pointer-events: none;
opacity: 0;
}
&[data-state='opening'] {
animation: ${animations('emergeSlantFromBottom')};
}
&[data-state='closing'] {
animation: ${animations('diveSlant')};
}
`

export const MenuFullScreenContainer = styled.div`
export const FullScreenMenu = styled.ul`
margin: 0 calc(0px - var(--layout-right-padding)) 0
calc(0px - var(--layout-left-padding));
padding: 0;
`
184 changes: 69 additions & 115 deletions src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,60 @@
import useModal, { Modal as ModalType } from '@delangle/use-modal'
import { Modal } from '@delangle/use-modal'
import * as React from 'react'
import * as ReactDOM from 'react-dom'

import { isFunction } from '../_internal/data'
import { isClientSide } from '../_internal/ssr'
import { buildUseOnlyOpenedInstanceHook } from '../_internal/useOnlyOpenedInstance'
import { useWindowSize } from '../_internal/useWindowSize'
import { ANIMATION_DURATIONS } from '../animations'
import { breakpoints } from '../breakpoints'
import { Modal } from '../Modal'
import { TogglePanel } from '../TogglePanel'
import { withTriggerElement } from '../withTriggerElement'

import { MenuContext } from './Menu.context'
import { MenuInstance, MenuInnerProps } from './Menu.interface'
import {
MenuContent,
MenuContainer,
MenuFullScreenContainer,
MenuOverlay,
} from './Menu.style'

const useOnlyOneMenuOpened = buildUseOnlyOpenedInstanceHook<MenuInstance>()
import { MenuInstance, InnerMenuProps } from './Menu.interface'
import { FloatingMenu, FullScreenMenu } from './Menu.style'

const TRIGGER_MARGIN = 12

const InnerMenu = React.forwardRef<HTMLUListElement, MenuInnerProps>(
(props, ref) => {
const {
const useOnlyOneMenuOpened = buildUseOnlyOpenedInstanceHook<MenuInstance>()

export const InnerMenu = React.forwardRef<HTMLDivElement, InnerMenuProps>(
(
{
children,
open,
onClose,
triggerRef,
fullScreenOnMobile = false,
scrollable = false,
onClose,
open,
position = 'vertical',
withOverlay = true,
scrollable = false,
setPosition,
...rest
} = props
...props
},
ref
) => {
useOnlyOneMenuOpened({ open, onClose })

const size = useWindowSize()
useOnlyOneMenuOpened({ open, onClose })
const [positionStyle, setPositionStyle] = React.useState<
React.CSSProperties
>()

const modal = useModal<HTMLUListElement>({
ref,
open,
onClose,
persistent: false,
animated: true,
animationDuration: ANIMATION_DURATIONS.m,
})

const content = isFunction(children)
? children(modal as ModalType<HTMLUListElement>)
: children

const updatePosition = React.useCallback(() => {
if (!triggerRef?.current || !modal.ref?.current) {
return
}

const triggerDimensions = triggerRef.current.getBoundingClientRect()
const menuHeight = modal.ref.current.clientHeight
const menuWidth = modal.ref.current.clientWidth

if (isFunction(setPosition)) {
setPositionStyle(
setPosition({ triggerDimensions, menuHeight, menuWidth })
const getChildren = React.useCallback(
(modal: Modal<HTMLDivElement>) => {
const content = isFunction(children) ? children(modal) : children

return fullScreenOnMobile && size.width < breakpoints.raw.phone ? (
<FullScreenMenu>{content}</FullScreenMenu>
) : (
<FloatingMenu data-scrollable={scrollable}>{content}</FloatingMenu>
)
} else {
},
[children]
)

const setStyle = React.useCallback(
(dimensions: DOMRect, triggerDimensions: DOMRect) => {
const menuHeight = triggerDimensions.height
const menuWidth = triggerDimensions.width

if (isFunction(setPosition)) {
return setPosition({ triggerDimensions, menuHeight, menuWidth })
}

if (position === 'vertical') {
let top = triggerDimensions.bottom + TRIGGER_MARGIN

Expand All @@ -89,72 +72,43 @@ const InnerMenu = React.forwardRef<HTMLUListElement, MenuInnerProps>(
? triggerDimensions.left - menuWidth + triggerDimensions.width
: triggerDimensions.left

setPositionStyle({ top, left, minWidth: triggerDimensions.width })
} else {
const top =
triggerDimensions.top + menuHeight > window.innerHeight
? triggerDimensions.top + triggerDimensions.height - menuHeight
: triggerDimensions.top
return { top, left, minWidth: triggerDimensions.width }
}

let left = triggerDimensions.right + TRIGGER_MARGIN
const top =
triggerDimensions.top + menuHeight > window.innerHeight
? triggerDimensions.top + triggerDimensions.height - menuHeight
: triggerDimensions.top

if (left + menuWidth > window.innerWidth) {
const leftWithMenuLeftOfTrigger =
triggerDimensions.left - menuWidth - TRIGGER_MARGIN
let left = triggerDimensions.right + TRIGGER_MARGIN

if (leftWithMenuLeftOfTrigger > 0) {
left = leftWithMenuLeftOfTrigger
}
}
if (left + menuWidth > window.innerWidth) {
const leftWithMenuLeftOfTrigger =
triggerDimensions.left - menuWidth - TRIGGER_MARGIN

setPositionStyle({ top, left })
if (leftWithMenuLeftOfTrigger > 0) {
left = leftWithMenuLeftOfTrigger
}
}
}
}, [triggerRef, modal.ref, setPosition, position])

React.useEffect(() => {
if (open) {
updatePosition()
}
}, [open, updatePosition])

React.useEffect(() => {
updatePosition()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [size, children])

if (!isClientSide) {
return null
}

if (fullScreenOnMobile && size.width < breakpoints.raw.phone) {
return (
<MenuContext.Provider value={modal}>
<Modal open={open} onClose={onClose}>
<MenuFullScreenContainer>{content}</MenuFullScreenContainer>
</Modal>
</MenuContext.Provider>
)
}

return ReactDOM.createPortal(
<MenuContext.Provider value={modal}>
{withOverlay && modal.state === 'opened' && <MenuOverlay />}
<MenuContainer
data-state={modal.state}
style={positionStyle}
ref={modal.ref}
data-testid="menu-container"
{...rest}
>
<MenuContent data-scrollable={scrollable}>{content}</MenuContent>
</MenuContainer>
</MenuContext.Provider>,
document.body

return { top, left }
},
[position, setPosition]
)

return (
<TogglePanel
children={getChildren}
data-testid="menu-container"
fullScreenOnMobile={fullScreenOnMobile}
open={open}
onClose={onClose}
ref={ref}
setStyle={setStyle}
{...props}
/>
)
}
)

export const Menu = withTriggerElement<HTMLUListElement>({ fowardRef: true })<
MenuInnerProps
>(InnerMenu)
export const Menu = withTriggerElement({ forwardRef: true })(InnerMenu)
2 changes: 1 addition & 1 deletion src/NavBar/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { NavBar } from './NavBar'

export { NavBarProps } from './NavBar.interface'
export type { NavBarProps } from './NavBar.interface'
8 changes: 0 additions & 8 deletions src/NavBarMenuItem/NavBarMenuItem.style.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import styled from 'styled-components'

import { MenuTriggerContainer } from '../Menu/Menu.style'

export const NavBarMenuItemContainer = styled.div`
& > ${MenuTriggerContainer} {
width: 100%;
height: 100%;
display: block;
}
&[data-bottom='true'] {
margin-top: auto;
}
Expand Down
4 changes: 2 additions & 2 deletions src/NavBarMenuItem/NavBarMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { NavBarItem } from '../NavBarItem'
import { NavBarMenuItemProps } from './NavBarMenuItem.interface'
import { NavBarMenuItemContainer } from './NavBarMenuItem.style'

const Content: React.FunctionComponent<{ modal: Modal<HTMLUListElement> }> = ({
const Content: React.FunctionComponent<{ modal: Modal<HTMLDivElement> }> = ({
modal,
children,
}) => {
Expand All @@ -24,7 +24,7 @@ const Content: React.FunctionComponent<{ modal: Modal<HTMLUListElement> }> = ({
}

export const NavBarMenuItem = React.forwardRef<
HTMLUListElement,
HTMLDivElement,
NavBarMenuItemProps
>(({ children, bottom, ...props }, ref) => (
<NavBarMenuItemContainer data-bottom={bottom}>
Expand Down
5 changes: 1 addition & 4 deletions src/Provider/Provider.context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as React from 'react'

export type ProviderContextValue = {
confirmLabel: string
cancelLabel: string
}
import { ProviderContextValue } from './Provider.interface'

export const ProviderContext = React.createContext<ProviderContextValue>({
confirmLabel: 'Valider',
Expand Down
5 changes: 5 additions & 0 deletions src/Provider/Provider.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export interface ProviderContextValue {
confirmLabel: string
cancelLabel: string
}

export interface ProviderProps {}

export type subscriptionCallback<Message, Options> = (
Expand Down
5 changes: 2 additions & 3 deletions src/Provider/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { Provider } from './Provider'
export { ProviderContext } from './Provider.context'

export { ProviderProps } from './Provider.interface'

export { ProviderContext, ProviderContextValue } from './Provider.context'
export type { ProviderContextValue, ProviderProps } from './Provider.interface'
Loading

0 comments on commit 61aa246

Please sign in to comment.