Skip to content

Commit

Permalink
feat(Portal): New component Portal
Browse files Browse the repository at this point in the history
remove usePortal, related components use Portal refactoring (include Command Dialog Drawer Tooltip
...)

re #1
  • Loading branch information
xinyao27 committed Jul 31, 2019
1 parent 820b6a3 commit 96b8d4b
Show file tree
Hide file tree
Showing 22 changed files with 190 additions and 130 deletions.
1 change: 1 addition & 0 deletions packages/fluent-ui-hooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"react-dom": ">= 16.8.0"
},
"dependencies": {
"@fluent-ui/core": "^0.8.0",
"json2mq": "^0.2.0",
"popper.js": "^1.15.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/fluent-ui-hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export * from './useAction'
export * from './useClickOutside'
export * from './usePortal'
export * from './useReveal'
export * from './usePopper'
export * from './useHover'
export * from './useClick'
export * from './useTouch'
export * from './useFocus'
export * from './useMedia'
export * from './useGlobal'
2 changes: 1 addition & 1 deletion packages/fluent-ui-hooks/src/useClickOutside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, RefObject } from 'react'

export function useClickOutside(
ref: RefObject<HTMLDivElement>,
handler: (e?: MouseEvent | TouchEvent) => void
handler: (e: MouseEvent | TouchEvent) => void
): void {
useEffect((): (() => void) => {
const listener = (event: MouseEvent | TouchEvent): void => {
Expand Down
3 changes: 3 additions & 0 deletions packages/fluent-ui-hooks/src/useGlobal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function useGlobal(): Window | NodeJS.Global {
return typeof window !== 'undefined' ? window : global
}
5 changes: 3 additions & 2 deletions packages/fluent-ui-hooks/src/useMedia/useMedia.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import * as json2mq from 'json2mq'
import { global } from '../utils'
import { useGlobal } from '../useGlobal'

type AllBreakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export type MediaQuery = json2mq.QueryObject | json2mq.QueryObject[] | AllBreakpoints | string
Expand Down Expand Up @@ -51,14 +51,15 @@ export function useMedia(
)
const transformedModel = React.useMemo((): TransformedModel => model(breakpoints), [breakpoints])

const global = useGlobal() as Window
const query = isBreakpoint
? transformedModel[mediaQuery as keyof Breakpoints]
: typeof mediaQuery === 'string'
? mediaQuery
: json2mq(mediaQuery)
const mql = global && global.matchMedia && global.matchMedia(query)

if (mql === false) return false
if (!mql) return false

// eslint-disable-next-line
const [state, setState] = React.useState((): boolean => mql.matches)
Expand Down
8 changes: 4 additions & 4 deletions packages/fluent-ui-hooks/src/usePopper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import PopperJS from 'popper.js'
// eslint-disable-next-line
export interface usePropperOptions extends PopperJS.PopperOptions {}

export function usePopper({
export function usePopper<Reference, Popper>({
placement = 'bottom',
positionFixed = true,
eventsEnabled = true,
...otherOptions
}: usePropperOptions): [React.MutableRefObject<null>, React.MutableRefObject<null>] {
}: usePropperOptions): [React.RefObject<Reference>, React.RefObject<Popper>] {
const popperInstance = React.useRef<PopperJS>(null)
const referenceRef = React.useRef(null)
const popperRef = React.useRef(null)
const referenceRef = React.useRef<Reference>(null)
const popperRef = React.useRef<Popper>(null)

React.useEffect((): (() => void) | void => {
if (popperInstance.current !== null) {
Expand Down
58 changes: 0 additions & 58 deletions packages/fluent-ui-hooks/src/usePortal.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/fluent-ui-hooks/src/utils.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Navigation from '@fluent-ui/core/Navigation'
import { Navigation } from '@fluent-ui/core'
```

Use `Navigation.Header` `Navigation.Footer` inside Navigation so that f knows how to render to the target location.
Use `Navigation.Header` `Navigation.Footer` inside Navigation so that we knows how to render to the target location.

### Props

Expand Down
38 changes: 38 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Portal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: Portal
components: Portal
type: Utils
---

# Portal

<p class="description">The portal component renders its children into a new "subtree" outside of current component hierarchy.
</p>

## Simple Portal

```jsx
() => {
const [show, setShow] = React.useState(false)
const container = React.useRef(null)

function handleClick() {
setShow(!show)
}

return (
<Box>
<Button onClick={handleClick}>{show ? 'Unmount' : 'Mount'}</Button>
<Box>
<Typography>It looks like I will render here?</Typography>
{show ? (
<Portal container={container.current}>
<Typography>Actually render here!</Typography>
</Portal>
) : null}
</Box>
<Box ref={container} />
</Box>
)
}
```
21 changes: 21 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Portal/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: Portal
components: Portal
api: true
---

## API

```
import Portal from '@fluent-ui/core/Portal'
// or
import { Portal } from '@fluent-ui/core'
```

### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| children&nbsp;* | React.ReactElement | | The children to render into the `container`. |
| container | Element &or; (() => Element) &or; null | | A node, component instance, or function that returns either. The `container` will have the portal children appended to it. By default, it uses the body of the top-level document object, so it's simply `document.body` most of the time. |
| disablePortal | boolean | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. |
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type: DataDisplay
color="white.default"
boxShadow="1"
textAlign="center"
zIndex="1001"
>
hello
</Box>
Expand Down
1 change: 1 addition & 0 deletions packages/fluent-ui/src/Command/Command.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export const StyledSecondaryContainer = styled(Box).attrs(
display: flex;
flex-direction: column;
width: 130px;
z-index: 1001;
`
60 changes: 29 additions & 31 deletions packages/fluent-ui/src/Command/Command.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { More as MoreIcon } from '@fluent-ui/icons' // TODO treeShaking
import { usePortal, useClickOutside, useReveal } from '@fluent-ui/hooks' // TODO treeShaking
import { useClickOutside, useReveal, usePopper } from '@fluent-ui/hooks' // TODO treeShaking
import { omit } from '../utils'
import {
StyledContent,
Expand All @@ -11,6 +11,8 @@ import {
import Secondary from './components/Secondary'
import Content from './components/Content'
import CommandButton from '../CommandButton'
import Portal from '../Portal'
import Transition from '../Transition'
import { CommandProps, CommandContainer, CommandChild, CommandType } from './Command.type'

export const CommandContext = React.createContext(false)
Expand All @@ -35,34 +37,24 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>(
}
)

// Secondary Popup related
const {
// visible: secondaryVisible,
setVisible: setSecondaryVisible,
Portal: SecondaryPortal
} = usePortal()
const [portalStyle, setPortalStyle] = React.useState()
function handleSecondaryVisible(e: React.MouseEvent<HTMLButtonElement>): void {
const rect = e.currentTarget.getBoundingClientRect()
const position = {
position: 'absolute',
left: `${rect.left + window.scrollX}px`,
top: `${rect.top + window.scrollY + rect.height}px`,
zIndex: 9999
}
setSecondaryVisible((visible: boolean): boolean => !visible)
setPortalStyle(position)
}

// Reveal does not take effect when using acrylic
reveal = acrylic ? false : reveal
const [RevealWrapper] = useReveal()

// Secondary Popup related
const [secondaryVisible, setSecondaryVisible] = React.useState(false)
function handleSecondaryVisible(): void {
if (secondaryVisible) return
setSecondaryVisible((visible: boolean): boolean => !visible)
}
// Click on the area outside the More menu to close the More menu.
const secondaryRef = React.useRef<HTMLDivElement>(null)
const [referenceRef, popperRef] = usePopper<HTMLButtonElement, HTMLDivElement>({
placement: 'bottom'
})
useClickOutside(
secondaryRef,
(): void => {
popperRef,
(event): void => {
// @ts-ignore
if (!referenceRef.current || referenceRef.current.contains(event.target)) return
setSecondaryVisible((visible: boolean): boolean => !visible)
}
)
Expand All @@ -82,21 +74,27 @@ const Command = React.forwardRef<HTMLDivElement, CommandProps>(
{!!container.secondary.length &&
(reveal ? (
<RevealWrapper>
<CommandButton style={{ height: '100%' }} onClick={handleSecondaryVisible}>
<CommandButton
ref={referenceRef}
style={{ height: '100%' }}
onClick={handleSecondaryVisible}
>
<MoreIcon />
</CommandButton>
</RevealWrapper>
) : (
<CommandButton onClick={handleSecondaryVisible}>
<CommandButton ref={referenceRef} onClick={handleSecondaryVisible}>
<MoreIcon />
</CommandButton>
))}

<SecondaryPortal style={portalStyle}>
<StyledSecondaryContainer ref={secondaryRef} acrylic={acrylic}>
{container.secondary}
</StyledSecondaryContainer>
</SecondaryPortal>
<Portal>
<Transition visible={secondaryVisible} wrapper={false} mountOnEnter unmountOnExit>
<StyledSecondaryContainer ref={popperRef} acrylic={acrylic}>
{container.secondary}
</StyledSecondaryContainer>
</Transition>
</Portal>
</CommandContext.Provider>
</StyledContainer>
)
Expand Down
19 changes: 9 additions & 10 deletions packages/fluent-ui/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { usePortal } from '@fluent-ui/hooks'
import { StyledDialogMask, StyledDialog, StyledDialogContent } from './Dialog.styled'
import Portal from '../Portal'
import Transition from '../Transition'
import Title from './components/Title'
import Content from './components/Content'
Expand All @@ -19,7 +19,6 @@ export const DialogContext = React.createContext<DialogContextType>({

const Dialog = React.forwardRef<HTMLDivElement, DialogProps>(
({ children, visible, onChange }: DialogProps, ref): React.ReactElement | null => {
const { Portal } = usePortal({ defaultVisible: true })
function handleClose(): void {
onChange && onChange(false)
}
Expand Down Expand Up @@ -48,13 +47,13 @@ const Dialog = React.forwardRef<HTMLDivElement, DialogProps>(

return (
<>
<Transition visible={visible} wrapper={false} mountOnEnter unmountOnExit>
<Portal>
<Portal>
<Transition visible={visible} wrapper={false} mountOnEnter unmountOnExit>
<StyledDialogMask onClick={handleClose} />
</Portal>
</Transition>
<Transition visible={visible} wrapper={false} mountOnEnter unmountOnExit>
<Portal>
</Transition>
</Portal>
<Portal>
<Transition visible={visible} wrapper={false} mountOnEnter unmountOnExit>
<StyledDialog
ref={ref}
boxShadow="5"
Expand All @@ -68,8 +67,8 @@ const Dialog = React.forwardRef<HTMLDivElement, DialogProps>(
{!!container.actions && container.actions}
</DialogContext.Provider>
</StyledDialog>
</Portal>
</Transition>
</Transition>
</Portal>
</>
)
}
Expand Down
Loading

0 comments on commit 96b8d4b

Please sign in to comment.