Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- ESC key pressed on a trigger element should propagate event if `Popup` is closed @sophieH29 ([#1373](https://github.com/stardust-ui/react/pull/1373))

### Features
- Add keyboard navigation and screen reader support for `Accordion` @silviuavram ([#1322](https://github.com/stardust-ui/react/pull/1322))
- Add `expanded` prop to `Accordion` @silviuavram ([#1322](https://github.com/stardust-ui/react/pull/1322))
- Add keyboard navigation and screen reader support for `Accordion` @silviuavram ([#1322](https://github.com/stardust-ui/react/pull/1322))
- Add `expanded` prop to `Accordion` @silviuavram ([#1322](https://github.com/stardust-ui/react/pull/1322))
- Replace `react-popper` package with custom `Popper` component and exposed as `UNSTABLE_Popper` positioning helper @Bugaa92 ([#1358](https://github.com/stardust-ui/react/pull/1358))

### Fixes
- Changing icon behavior as for some cases icon could be visible ([#1327](https://github.com/stardust-ui/react/pull/1327))
Expand Down
91 changes: 37 additions & 54 deletions docs/src/prototypes/MenuButton/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {
MenuProps,
Ref,
ShorthandValue,
Alignment,
Position,
UNSTABLE_Popper,
} from '@stardust-ui/react'
import * as _ from 'lodash'
import * as keyboardKey from 'keyboard-key'
import * as PopperJS from 'popper.js'
import * as React from 'react'
import { Manager as PopperManager, Reference as PopperReference, Popper } from 'react-popper'

import { focusMenuItem, focusNearest } from './focusUtils'
import menuButtonBehavior from './menuButtonBehavior'
Expand Down Expand Up @@ -44,15 +46,15 @@ class MenuButton extends React.Component<MenuButtonProps, MenuButtonState> {
menuOpen: false,
}

buttonNode: HTMLButtonElement
menuNode: HTMLUListElement
buttonRef = React.createRef<HTMLButtonElement>()
menuRef = React.createRef<HTMLUListElement>()

componentDidUpdate(_, prevState: MenuButtonState) {
if (!prevState.menuOpen && this.state.menuOpen) {
document.addEventListener('click', this.handleDocumentClick)

focusMenuItem(
this.menuNode,
this.menuRef.current,
this.state.lastKeyCode === keyboardKey.ArrowUp ? 'last' : 'first',
)
}
Expand All @@ -63,11 +65,11 @@ class MenuButton extends React.Component<MenuButtonProps, MenuButtonState> {
switch (this.state.lastKeyCode) {
case keyboardKey.Enter:
case keyboardKey.Escape:
this.buttonNode.focus()
this.buttonRef.current.focus()
break

case keyboardKey.Tab:
focusNearest(this.buttonNode, this.state.lastShiftKey ? 'previous' : 'next')
focusNearest(this.buttonRef.current, this.state.lastShiftKey ? 'previous' : 'next')
break
}
}
Expand All @@ -87,7 +89,8 @@ class MenuButton extends React.Component<MenuButtonProps, MenuButtonState> {
const { menuOpen } = this.state
const target = e.target as HTMLElement
const isInside =
_.invoke(this.buttonNode, 'contains', target) || _.invoke(this.menuNode, 'contains', target)
_.invoke(this.buttonRef.current, 'contains', target) ||
_.invoke(this.menuRef.current, 'contains', target)

if (menuOpen && !isInside) {
this.setState({ lastKeyCode: null, menuOpen: false })
Expand Down Expand Up @@ -146,6 +149,7 @@ class MenuButton extends React.Component<MenuButtonProps, MenuButtonState> {
render() {
const { button, disabled, menu, placement } = this.props
const { menuOpen } = this.state
const [position, align] = _.split(placement, '-') as [Position, Alignment]
const accessibilityBehavior: AccessibilityBehavior = menuButtonBehavior({
...this.props,
...this.state,
Expand All @@ -156,53 +160,32 @@ class MenuButton extends React.Component<MenuButtonProps, MenuButtonState> {
onKeyDown={this.handleKeyDown}
style={{ boxSizing: 'border-box', display: 'inline-block' }}
>
<PopperManager>
<PopperReference>
{({ ref }) => (
<Ref
innerRef={(buttonNode: HTMLButtonElement) => {
this.buttonNode = buttonNode
ref(buttonNode)
}}
>
{Button.create(button, {
defaultProps: {
...accessibilityBehavior.attributes.button,
disabled,
},
overrideProps: this.handleButtonOverrides,
})}
</Ref>
)}
</PopperReference>
<Popper placement={placement}>
{({ placement, ref, style }) =>
menuOpen && (
<Ref
innerRef={(menuNode: HTMLUListElement) => {
this.menuNode = menuNode
ref(menuNode)
}}
>
{Menu.create(menu, {
defaultProps: {
...accessibilityBehavior.attributes.menu,
'data-placement': placement,
styles: { background: '#fff', zIndex: 1 },
vertical: true,
},
overrideProps: {
items: this.handleMenuItemOverrides(
accessibilityBehavior.attributes.menuItem,
),
style,
},
})}
</Ref>
)
}
</Popper>
</PopperManager>
<Ref innerRef={this.buttonRef}>
{Button.create(button, {
defaultProps: {
...accessibilityBehavior.attributes.button,
disabled,
},
overrideProps: this.handleButtonOverrides,
})}
</Ref>
{menuOpen && (
<UNSTABLE_Popper align={align} position={position} targetRef={this.buttonRef}>
<Ref innerRef={this.menuRef}>
{Menu.create(menu, {
defaultProps: {
...accessibilityBehavior.attributes.menu,
'data-placement': placement,
styles: { background: '#fff', zIndex: 1 },
vertical: true,
},
overrideProps: {
items: this.handleMenuItemOverrides(accessibilityBehavior.attributes.menuItem),
},
})}
</Ref>
</UNSTABLE_Popper>
)}
</div>
)
}
Expand Down
25 changes: 12 additions & 13 deletions docs/src/prototypes/MenuButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,18 @@ class MenuButtonPrototype extends React.Component<{}, MenuButtonPrototypeState>
<Header as="h4" content="Choose placement:" />
<Dropdown
items={[
'auto',
'top',
'top-start',
'top-end',
'right',
'right-start',
'right-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'above-start',
'above-center',
'above-end',
'below-start',
'below-center',
'below-end',
'before-top',
'before-center',
'before-bottom',
'after-top',
'after-center',
'after-bottom',
]}
onSelectedChange={this.handleChange}
value={placement}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
"keyboard-key": "^1.0.1",
"lodash": "^4.17.11",
"mdn-polyfills": "^5.15.0",
"popper.js": "^1.14.6",
"prop-types": "^15.6.1",
"react-fela": "^10.2.0",
"react-is": "^16.6.3",
"react-popper": "^1.3.2",
"tslib": "^1.9.3"
},
"devDependencies": {
Expand Down
80 changes: 20 additions & 60 deletions packages/react/src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as ReactDOM from 'react-dom'
import * as PropTypes from 'prop-types'
import * as keyboardKey from 'keyboard-key'
import * as _ from 'lodash'
import { Popper, PopperChildrenProps } from 'react-popper'

import {
applyAccessibilityKeyHandlers,
Expand All @@ -24,12 +23,14 @@ import {
setWhatInputSource,
} from '../../lib'
import { ComponentEventHandler, ShorthandValue } from '../../types'

import { getPopupPlacement, applyRtlToOffset, Alignment, Position } from './positioningHelper'
import createPopperReferenceProxy from './createPopperReferenceProxy'

import {
ALIGNMENTS,
POSITIONS,
Popper,
PositioningProps,
PopperChildrenProps,
} from '../../lib/positioner'
import PopupContent from './PopupContent'

import { popupBehavior } from '../../lib/accessibility'
import {
AutoFocusZone,
Expand All @@ -44,9 +45,6 @@ import {
AccessibilityBehavior,
} from '../../lib/accessibility/types'

const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']

export type PopupEvents = 'click' | 'hover' | 'focus'
export type RestrictedClickEvents = 'click' | 'focus'
export type RestrictedHoverEvents = 'hover' | 'focus'
Expand All @@ -59,17 +57,15 @@ export interface PopupSlotClassNames {
export interface PopupProps
extends StyledComponentProps<PopupProps>,
ChildrenComponentProps,
ContentComponentProps<ShorthandValue> {
ContentComponentProps<ShorthandValue>,
PositioningProps {
/**
* Accessibility behavior if overridden by the user.
* @default popupBehavior
* @available popupFocusTrapBehavior, dialogBehavior
* */
accessibility?: Accessibility

/** Alignment for the popup. */
align?: Alignment

/** Additional CSS class name(s) to apply. */
className?: string

Expand All @@ -88,15 +84,6 @@ export interface PopupProps
/** Delay in ms for the mouse leave event, before the popup will be closed. */
mouseLeaveDelay?: number

/** Offset value to apply to rendered popup. Accepts the following units:
* - px or unit-less, interpreted as pixels
* - %, percentage relative to the length of the trigger element
* - %p, percentage relative to the length of the popup element
* - vw, CSS viewport width unit
* - vh, CSS viewport height unit
*/
offset?: string

/** Events triggering the popup. */
on?: PopupEvents | PopupEventsArray

Expand All @@ -113,13 +100,6 @@ export interface PopupProps
/** A popup can show a pointer to trigger. */
pointing?: boolean

/**
* Position for the popup. Position has higher priority than align. If position is vertical ('above' | 'below')
* and align is also vertical ('top' | 'bottom') or if both position and align are horizontal ('before' | 'after'
* and 'start' | 'end' respectively), then provided value for 'align' will be ignored and 'center' will be used instead.
*/
position?: Position

/**
* Function to render popup content.
* @param {Function} updatePosition - function to request popup position update.
Expand All @@ -140,7 +120,6 @@ export interface PopupProps

export interface PopupState {
open: boolean
target: HTMLElement
}

/**
Expand Down Expand Up @@ -171,6 +150,7 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
mountDocument: PropTypes.object,
mountNode: customPropTypes.domNode,
mouseLeaveDelay: PropTypes.number,
offset: PropTypes.string,
on: PropTypes.oneOfType([
PropTypes.oneOf(['hover', 'click', 'focus']),
PropTypes.arrayOf(PropTypes.oneOf(['click', 'focus'])),
Expand Down Expand Up @@ -198,6 +178,7 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat

static autoControlledProps = ['open']

pointerTargetRef = React.createRef<HTMLElement>()
triggerRef = React.createRef<HTMLElement>() as React.MutableRefObject<HTMLElement>
// focusable element which has triggered Popup, can be either triggerDomElement or the element inside it
triggerFocusableDomElement = null
Expand Down Expand Up @@ -407,27 +388,17 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
rtl: boolean,
accessibility: AccessibilityBehavior,
): JSX.Element {
const { align, position, offset } = this.props
const { target } = this.props

const placement = getPopupPlacement({ align, position, rtl })

const popperModifiers = {
// https://popper.js.org/popper-documentation.html#modifiers..offset
...(offset && {
offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
keepTogether: { enabled: false },
}),
}

const referenceElement = createPopperReferenceProxy(target || this.triggerRef)
const { align, position, offset, target } = this.props

return (
<Popper
placement={placement}
referenceElement={referenceElement}
pointerTargetRef={this.pointerTargetRef}
align={align}
position={position}
offset={offset}
rtl={rtl}
targetRef={target ? toRefObject(target) : this.triggerRef}
children={this.renderPopperChildren.bind(this, popupPositionClasses, rtl, accessibility)}
modifiers={popperModifiers}
/>
)
}
Expand All @@ -436,17 +407,9 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
popupPositionClasses: string,
rtl: boolean,
accessibility: AccessibilityBehavior,
// https://popper.js.org/popper-documentation.html#Popper.scheduleUpdate
{
arrowProps,
placement,
ref,
scheduleUpdate,
style: popupPlacementStylesRaw,
}: PopperChildrenProps,
{ placement, scheduleUpdate }: PopperChildrenProps,
) => {
const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
const popupPlacementStyles = _.omitBy(popupPlacementStylesRaw, _.isNaN)
const content = renderContent ? renderContent(scheduleUpdate) : propsContent
const documentRef = toRefObject(mountDocument)

Expand All @@ -455,7 +418,6 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
...accessibility.attributes.popup,
...accessibility.keyHandlers.popup,
className: popupPositionClasses,
style: popupPlacementStyles,
...this.getContentProps(),
}

Expand All @@ -481,8 +443,7 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
...popupContentAttributes,
placement,
pointing,
pointerRef: arrowProps.ref,
pointerStyle: arrowProps.style,
pointerRef: this.pointerTargetRef,
},
overrideProps: this.getContentProps,
})
Expand All @@ -493,7 +454,6 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
<>
<Ref
innerRef={domElement => {
ref(domElement)
this.popupDomElement = domElement
handleRef(contentRef, domElement)
handleRef(nestingRef, domElement)
Expand Down
Loading