From 89a724a5607fb995807c4d12d9ea3f4862090260 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 9 May 2019 19:48:51 +0200
Subject: [PATCH 01/30] feat(dropdown): align, position, offset props +
automatic position
---
.../DropdownExampleOffset.shorthand.tsx | 12 ++
.../DropdownExamplePosition.shorthand.tsx | 38 ++++++
.../components/Dropdown/Variations/index.tsx | 10 ++
packages/react-component-ref/src/Ref.tsx | 2 +-
.../src/components/Dropdown/Dropdown.tsx | 74 ++++++++---
packages/react/src/components/Popup/Popup.tsx | 59 ++-------
packages/react/src/index.ts | 2 +-
.../react/src/lib/positioner/Positioner.tsx | 64 +++++++++
.../src/lib/positioner/UpdatableComponent.tsx | 43 ++++++
.../positioner}/createPopperReferenceProxy.ts | 6 +-
packages/react/src/lib/positioner/index.ts | 7 +
.../positioner}/positioningHelper.ts | 6 +-
.../specs/components/Popup/Popup-test.tsx | 102 ---------------
.../lib/positioner/positioningHelper-test.ts | 122 ++++++++++++++++++
14 files changed, 370 insertions(+), 177 deletions(-)
create mode 100644 docs/src/examples/components/Dropdown/Variations/DropdownExampleOffset.shorthand.tsx
create mode 100644 docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
create mode 100644 packages/react/src/lib/positioner/Positioner.tsx
create mode 100644 packages/react/src/lib/positioner/UpdatableComponent.tsx
rename packages/react/src/{components/Popup => lib/positioner}/createPopperReferenceProxy.ts (92%)
create mode 100644 packages/react/src/lib/positioner/index.ts
rename packages/react/src/{components/Popup => lib/positioner}/positioningHelper.ts (93%)
create mode 100644 packages/react/test/specs/lib/positioner/positioningHelper-test.ts
diff --git a/docs/src/examples/components/Dropdown/Variations/DropdownExampleOffset.shorthand.tsx b/docs/src/examples/components/Dropdown/Variations/DropdownExampleOffset.shorthand.tsx
new file mode 100644
index 0000000000..1ad84e8708
--- /dev/null
+++ b/docs/src/examples/components/Dropdown/Variations/DropdownExampleOffset.shorthand.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react'
+import { Grid, Dropdown } from '@stardust-ui/react'
+
+const inputItems = ['Bruce Wayne', 'Natasha Romanoff', 'Steven Strange', 'Alfred Pennyworth']
+
+const DropdownExamplePosition = () => (
+
+
+
+)
+
+export default DropdownExamplePosition
diff --git a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
new file mode 100644
index 0000000000..fa68a71939
--- /dev/null
+++ b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react'
+import { Grid, Dropdown } from '@stardust-ui/react'
+
+const inputItems = ['Bruce Wayne', 'Natasha Romanoff', 'Steven Strange', 'Alfred Pennyworth']
+
+const DropdownArrowExample = props => {
+ const { position, align } = props
+
+ return (
+
+ )
+}
+
+const triggers = [
+ { position: 'above', align: 'start' },
+ { position: 'below', align: 'start' },
+ { position: 'above', align: 'end' },
+ { position: 'below', align: 'end' },
+ { position: 'after', align: 'top' },
+ { position: 'before', align: 'top' },
+ { position: 'after', align: 'bottom' },
+ { position: 'before', align: 'bottom' },
+]
+
+const DropdownExamplePosition = () => (
+
+ {triggers.map(({ position, align }) => (
+
+ ))}
+
+)
+
+export default DropdownExamplePosition
diff --git a/docs/src/examples/components/Dropdown/Variations/index.tsx b/docs/src/examples/components/Dropdown/Variations/index.tsx
index f9978ec1b5..52a97965ec 100644
--- a/docs/src/examples/components/Dropdown/Variations/index.tsx
+++ b/docs/src/examples/components/Dropdown/Variations/index.tsx
@@ -19,6 +19,16 @@ const Variations = () => (
description="A multiple search dropdown that uses French to provide information and accessibility."
examplePath="components/Dropdown/Variations/DropdownExampleSearchMultipleFrenchLanguage"
/>
+
+
)
diff --git a/packages/react-component-ref/src/Ref.tsx b/packages/react-component-ref/src/Ref.tsx
index ee2face475..27afbacfca 100644
--- a/packages/react-component-ref/src/Ref.tsx
+++ b/packages/react-component-ref/src/Ref.tsx
@@ -14,7 +14,7 @@ export interface RefProps {
*
* @param {HTMLElement} node - Referred node.
*/
- innerRef: React.Ref
+ innerRef: React.Ref
}
const Ref: React.FunctionComponent = props => {
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 15f5bf9656..8d48c18974 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -32,7 +32,7 @@ import {
UIComponentProps,
isFromKeyboard,
} from '../../lib'
-import List from '../List/List'
+import List, { ListProps } from '../List/List'
import DropdownItem, { DropdownItemProps } from './DropdownItem'
import DropdownSelectedItem, { DropdownSelectedItemProps } from './DropdownSelectedItem'
import DropdownSearchInput, { DropdownSearchInputProps } from './DropdownSearchInput'
@@ -41,6 +41,13 @@ import { screenReaderContainerStyles } from '../../lib/accessibility/Styles/acce
import ListItem from '../List/ListItem'
import Icon, { IconProps } from '../Icon/Icon'
import Portal from '../Portal/Portal'
+import {
+ ALIGNMENTS,
+ POSITIONS,
+ Positioner,
+ PositionCommonProps,
+ UpdatableComponent,
+} from '../../lib/positioner'
export interface DropdownSlotClassNames {
clearIndicator: string
@@ -54,7 +61,9 @@ export interface DropdownSlotClassNames {
triggerButton: string
}
-export interface DropdownProps extends UIComponentProps {
+export interface DropdownProps
+ extends UIComponentProps,
+ PositionCommonProps {
/** The index of the currently active selected item, if dropdown has a multiple selection. */
activeSelectedIndex?: number
@@ -238,6 +247,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
content: false,
}),
activeSelectedIndex: PropTypes.number,
+ align: PropTypes.oneOf(_.without(ALIGNMENTS)),
clearable: PropTypes.bool,
clearIndicator: customPropTypes.itemShorthand,
defaultActiveSelectedIndex: PropTypes.number,
@@ -266,6 +276,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
onSelectedChange: PropTypes.func,
open: PropTypes.bool,
placeholder: PropTypes.string,
+ position: PropTypes.oneOf(POSITIONS),
renderItem: PropTypes.func,
renderSelectedItem: PropTypes.func,
search: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
@@ -280,6 +291,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
}
static defaultProps = {
+ align: 'start',
as: 'div',
clearIndicator: 'stardust-close',
itemToString: item => {
@@ -290,6 +302,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
// targets DropdownItem shorthand objects
return (item as any).header || String(item)
},
+ position: 'below',
toggleIndicator: {},
triggerButton: {},
}
@@ -472,7 +485,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
},
}),
})}
- {this.renderItemsList(
+ {this.preparePropsAndRenderItemsList(
styles,
variables,
highlightedIndex,
@@ -482,6 +495,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
getItemProps,
getInputProps,
value,
+ rtl,
)}
@@ -592,7 +606,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
})
}
- private renderItemsList(
+ private preparePropsAndRenderItemsList(
styles: ComponentSlotStylesInput,
variables: ComponentVariablesInput,
highlightedIndex: number,
@@ -602,6 +616,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
getItemProps: (options: GetItemPropsOptions) => any,
getInputProps: (options?: GetInputPropsOptions) => any,
value: ShorthandValue | ShorthandCollection,
+ rtl: boolean,
) {
const { search } = this.props
const { open } = this.state
@@ -634,22 +649,49 @@ class Dropdown extends AutoControlledComponent, Dropdo
handleRef(innerRef, listElement)
}}
>
-
+ {this.renderItemsList(
+ {
+ className: Dropdown.slotClassNames.itemsList,
+ ...accessibilityMenuProps,
+ styles: styles.list,
+ tabIndex: search ? undefined : -1, // needs to be focused when trigger button is activated.
+ 'aria-hidden': !open,
+ onFocus: this.handleTriggerButtonOrListFocus,
+ onBlur: this.handleListBlur,
+ items: open
+ ? this.renderItems(styles, variables, getItemProps, highlightedIndex, value)
+ : [],
+ },
+ rtl,
+ )}
)
}
+ private renderItemsList(listProps: ListProps, rtl: boolean): JSX.Element {
+ const { align, position, offset } = this.props
+
+ return (
+ (
+
+ )}
+ />
+ )
+ }
+
private renderItems(
styles: ComponentSlotStylesInput,
variables: ComponentVariablesInput,
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index c0e265a3fb..0722c77142 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -7,7 +7,7 @@ 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 { PopperChildrenProps } from 'react-popper'
import {
applyAccessibilityKeyHandlers,
@@ -24,12 +24,8 @@ import {
setWhatInputSource,
} from '../../lib'
import { ComponentEventHandler, ShorthandValue } from '../../types'
-
-import { getPopupPlacement, applyRtlToOffset, Alignment, Position } from './positioningHelper'
-import createPopperReferenceProxy from './createPopperReferenceProxy'
-
+import { ALIGNMENTS, POSITIONS, Positioner, PositionCommonProps } from '../../lib/positioner'
import PopupContent from './PopupContent'
-
import { popupBehavior } from '../../lib/accessibility'
import {
AutoFocusZone,
@@ -44,9 +40,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'
@@ -59,7 +52,8 @@ export interface PopupSlotClassNames {
export interface PopupProps
extends StyledComponentProps,
ChildrenComponentProps,
- ContentComponentProps {
+ ContentComponentProps,
+ PositionCommonProps {
/**
* Accessibility behavior if overridden by the user.
* @default popupBehavior
@@ -67,9 +61,6 @@ export interface PopupProps
* */
accessibility?: Accessibility
- /** Alignment for the popup. */
- align?: Alignment
-
/** Additional CSS class name(s) to apply. */
className?: string
@@ -88,15 +79,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
@@ -113,13 +95,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.
@@ -140,7 +115,6 @@ export interface PopupProps
export interface PopupState {
open: boolean
- target: HTMLElement
}
/**
@@ -409,27 +383,16 @@ export default class Popup extends AutoControlledComponent
)
}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 666a82bc0f..e4fdc4e73a 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -113,7 +113,7 @@ export {
PopupEventsArray,
} from './components/Popup/Popup'
export { default as PopupContent, PopupContentProps } from './components/Popup/PopupContent'
-export { Placement, Alignment, Position } from './components/Popup/positioningHelper'
+export { Alignment, Position } from './lib/positioner'
export {
default as Portal,
diff --git a/packages/react/src/lib/positioner/Positioner.tsx b/packages/react/src/lib/positioner/Positioner.tsx
new file mode 100644
index 0000000000..f0577bf4e1
--- /dev/null
+++ b/packages/react/src/lib/positioner/Positioner.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react'
+import { Popper, PopperChildrenProps, PopperProps } from 'react-popper'
+import { Modifiers } from 'popper.js'
+
+import { Alignment, Position } from './index'
+import { getPlacement, applyRtlToOffset } from './positioningHelper'
+import createPopperReferenceProxy from './createPopperReferenceProxy'
+
+export interface PositionCommonProps {
+ /** Alignment for the component. */
+ align?: Alignment
+
+ /** Offset value to apply to rendered component. 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 component element
+ * - vw, CSS viewport width unit
+ * - vh, CSS viewport height unit
+ */
+ offset?: string
+
+ /**
+ * Position for the component. 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
+}
+
+interface PositionerProps extends PopperProps, PositionCommonProps {
+ /**
+ * Content for children using render props API
+ */
+ children: (props: PopperChildrenProps) => React.ReactNode
+
+ /**
+ * rtl attribute for the component
+ */
+ rtl?: boolean
+
+ target?: HTMLElement | React.RefObject
+}
+
+const Positioner: React.FunctionComponent = props => {
+ const { align, children, position, offset, rtl, target, ...rest } = props
+ // https://popper.js.org/popper-documentation.html#modifiers..offset
+ const popperModifiers: Modifiers = offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }
+
+ return (
+
+ )
+}
+
+export default Positioner
diff --git a/packages/react/src/lib/positioner/UpdatableComponent.tsx b/packages/react/src/lib/positioner/UpdatableComponent.tsx
new file mode 100644
index 0000000000..c53791c9f6
--- /dev/null
+++ b/packages/react/src/lib/positioner/UpdatableComponent.tsx
@@ -0,0 +1,43 @@
+import * as React from 'react'
+import { Ref } from '@stardust-ui/react-component-ref'
+
+import { Extendable } from '../../types'
+
+interface UpdatableListProps {
+ /**
+ * Component that will be rendered.
+ */
+ Component: React.ComponentType
+
+ /**
+ * Called when a child component will be mounted or updated.
+ *
+ * @param {HTMLElement} node - Referred node.
+ */
+ innerRef?: React.Ref
+
+ /**
+ * Function that will trigger the rerender.
+ */
+ scheduleUpdate: Function
+
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render.
+ */
+ updateDependencies: any[]
+}
+
+const UpdatableComponent: React.FunctionComponent> = props => {
+ const { Component, innerRef, scheduleUpdate, updateDependencies, ...rest } = props
+
+ React.useEffect(() => scheduleUpdate && scheduleUpdate(), updateDependencies)
+
+ if (!innerRef) return
+ return (
+ [
+
+ ]
+ )
+}
+
+export default UpdatableComponent
diff --git a/packages/react/src/components/Popup/createPopperReferenceProxy.ts b/packages/react/src/lib/positioner/createPopperReferenceProxy.ts
similarity index 92%
rename from packages/react/src/components/Popup/createPopperReferenceProxy.ts
rename to packages/react/src/lib/positioner/createPopperReferenceProxy.ts
index 59bd7249c7..334d129673 100644
--- a/packages/react/src/components/Popup/createPopperReferenceProxy.ts
+++ b/packages/react/src/lib/positioner/createPopperReferenceProxy.ts
@@ -4,11 +4,7 @@ import * as React from 'react'
import * as PopperJS from 'popper.js'
class ReferenceProxy implements PopperJS.ReferenceObject {
- ref: React.RefObject
-
- constructor(refObject) {
- this.ref = refObject
- }
+ constructor(private ref: React.RefObject) {}
getBoundingClientRect() {
return _.invoke(this.ref.current, 'getBoundingClientRect', {})
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
new file mode 100644
index 0000000000..a538bcac98
--- /dev/null
+++ b/packages/react/src/lib/positioner/index.ts
@@ -0,0 +1,7 @@
+export type Position = 'above' | 'below' | 'before' | 'after'
+export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
+export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
+
+export { default as Positioner, PositionCommonProps } from './Positioner'
+export { default as UpdatableComponent } from './UpdatableComponent'
diff --git a/packages/react/src/components/Popup/positioningHelper.ts b/packages/react/src/lib/positioner/positioningHelper.ts
similarity index 93%
rename from packages/react/src/components/Popup/positioningHelper.ts
rename to packages/react/src/lib/positioner/positioningHelper.ts
index 92e74d367a..8a32f642e6 100644
--- a/packages/react/src/components/Popup/positioningHelper.ts
+++ b/packages/react/src/lib/positioner/positioningHelper.ts
@@ -1,8 +1,6 @@
-export { Placement } from 'popper.js'
import { Placement } from 'popper.js'
-export type Position = 'above' | 'below' | 'before' | 'after'
-export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+import { Alignment, Position } from './index'
enum PlacementParts {
top = 'top',
@@ -56,7 +54,7 @@ const shouldAlignToCenter = (p: Position, a: Alignment) => {
* | after | center | right | left
* | after | bottom | right-end | left-end
*/
-export const getPopupPlacement = ({
+export const getPlacement = ({
align,
position,
rtl,
diff --git a/packages/react/test/specs/components/Popup/Popup-test.tsx b/packages/react/test/specs/components/Popup/Popup-test.tsx
index e392d2f93a..8c2239f712 100644
--- a/packages/react/test/specs/components/Popup/Popup-test.tsx
+++ b/packages/react/test/specs/components/Popup/Popup-test.tsx
@@ -1,12 +1,5 @@
-import { Placement } from 'popper.js'
import * as React from 'react'
-import {
- getPopupPlacement,
- applyRtlToOffset,
- Position,
- Alignment,
-} from 'src/components/Popup/positioningHelper'
import Popup, { PopupEvents } from 'src/components/Popup/Popup'
import { Accessibility } from 'src/lib/accessibility/types'
import { popupFocusTrapBehavior, popupBehavior, dialogBehavior } from 'src/lib/accessibility/index'
@@ -15,33 +8,9 @@ import { domEvent, mountWithProvider } from '../../../utils'
import * as keyboardKey from 'keyboard-key'
import { ReactWrapper } from 'enzyme'
-type PositionTestInput = {
- align: Alignment
- position: Position
- expectedPlacement: Placement
- rtl?: boolean
-}
-
describe('Popup', () => {
const triggerId = 'triggerElement'
const contentId = 'contentId'
- const testPopupPosition = ({
- align,
- position,
- expectedPlacement,
- rtl = false,
- }: PositionTestInput) =>
- it(`Popup ${position} position is transformed to ${expectedPlacement} Popper's placement`, () => {
- const actualPlacement = getPopupPlacement({ align, position, rtl })
- expect(actualPlacement).toEqual(expectedPlacement)
- })
-
- const testPopupPositionInRtl = ({
- align,
- position,
- expectedPlacement,
- }: PositionTestInput & { rtl?: never }) =>
- testPopupPosition({ align, position, expectedPlacement, rtl: true })
const getPopupContent = (popup: ReactWrapper) => {
return popup.find(`div#${contentId}`)
@@ -76,77 +45,6 @@ describe('Popup', () => {
expect(getPopupContent(popup).exists()).toBe(false)
}
- describe('handles Popup position correctly in ltr', () => {
- testPopupPosition({ position: 'above', align: 'start', expectedPlacement: 'top-start' })
- testPopupPosition({ position: 'above', align: 'center', expectedPlacement: 'top' })
- testPopupPosition({ position: 'above', align: 'end', expectedPlacement: 'top-end' })
- testPopupPosition({ position: 'below', align: 'start', expectedPlacement: 'bottom-start' })
- testPopupPosition({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
- testPopupPosition({ position: 'below', align: 'end', expectedPlacement: 'bottom-end' })
- testPopupPosition({ position: 'before', align: 'top', expectedPlacement: 'left-start' })
- testPopupPosition({ position: 'before', align: 'center', expectedPlacement: 'left' })
- testPopupPosition({ position: 'before', align: 'bottom', expectedPlacement: 'left-end' })
- testPopupPosition({ position: 'after', align: 'top', expectedPlacement: 'right-start' })
- testPopupPosition({ position: 'after', align: 'center', expectedPlacement: 'right' })
- testPopupPosition({ position: 'after', align: 'bottom', expectedPlacement: 'right-end' })
- })
-
- describe('handles Popup position correctly in rtl', () => {
- testPopupPositionInRtl({ position: 'above', align: 'start', expectedPlacement: 'top-end' })
- testPopupPositionInRtl({ position: 'above', align: 'center', expectedPlacement: 'top' })
- testPopupPositionInRtl({ position: 'above', align: 'end', expectedPlacement: 'top-start' })
- testPopupPositionInRtl({ position: 'below', align: 'start', expectedPlacement: 'bottom-end' })
- testPopupPositionInRtl({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
- testPopupPositionInRtl({ position: 'below', align: 'end', expectedPlacement: 'bottom-start' })
- testPopupPositionInRtl({ position: 'before', align: 'top', expectedPlacement: 'right-start' })
- testPopupPositionInRtl({ position: 'before', align: 'center', expectedPlacement: 'right' })
- testPopupPositionInRtl({ position: 'before', align: 'bottom', expectedPlacement: 'right-end' })
- testPopupPositionInRtl({ position: 'after', align: 'top', expectedPlacement: 'left-start' })
- testPopupPositionInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
- testPopupPositionInRtl({ position: 'after', align: 'bottom', expectedPlacement: 'left-end' })
- })
-
- describe('Popup offset transformed correctly in RTL', () => {
- it("applies transform only for 'above' and 'below' postioning", () => {
- const originalOffsetValue = '100%'
-
- expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
- expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
-
- expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
- expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
- })
-
- const expectOffsetTransformResult = (originalOffset, resultOffset) => {
- expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
- }
-
- it('flips sign of simple expressions', () => {
- expectOffsetTransformResult('100%', '-100%')
- expectOffsetTransformResult(' 2000%p ', '-2000%p')
- expectOffsetTransformResult('100 ', '-100')
- expectOffsetTransformResult(' - 200vh', '200vh')
- })
-
- it('flips sign of complex expressions', () => {
- expectOffsetTransformResult('100% + 200', '-100% - 200')
- expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
- })
-
- it('transforms only horizontal offset value', () => {
- const xOffset = '-100%'
- const yOffset = '800vh'
-
- const offsetValue = [xOffset, yOffset].join(',')
- const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
- ',',
- )
-
- expect(xOffsetTransformed.trim()).not.toBe(xOffset)
- expect(yOffsetTransformed.trim()).toBe(yOffset)
- })
- })
-
describe('onOpenChange', () => {
test('is called on click', () => {
const spy = jest.fn()
diff --git a/packages/react/test/specs/lib/positioner/positioningHelper-test.ts b/packages/react/test/specs/lib/positioner/positioningHelper-test.ts
new file mode 100644
index 0000000000..370b6e19dd
--- /dev/null
+++ b/packages/react/test/specs/lib/positioner/positioningHelper-test.ts
@@ -0,0 +1,122 @@
+import { Placement } from 'popper.js'
+
+import { Alignment, Position } from 'src/lib/positioner'
+import { getPlacement, applyRtlToOffset } from 'src/lib/positioner/positioningHelper'
+
+type PositionTestInput = {
+ align: Alignment
+ position: Position
+ expectedPlacement: Placement
+ rtl?: boolean
+}
+
+describe('positioningHelper', () => {
+ const testPositioningHelper = ({
+ align,
+ position,
+ expectedPlacement,
+ rtl = false,
+ }: PositionTestInput) =>
+ it(`positioningHelper ${position} position argument is transformed to ${expectedPlacement} Popper's placement`, () => {
+ const actualPlacement = getPlacement({ align, position, rtl })
+ expect(actualPlacement).toEqual(expectedPlacement)
+ })
+
+ const testPositioningHelperInRtl = ({
+ align,
+ position,
+ expectedPlacement,
+ }: PositionTestInput & { rtl?: never }) =>
+ testPositioningHelper({ align, position, expectedPlacement, rtl: true })
+
+ describe('handles positioningHelper position argument correctly in ltr', () => {
+ testPositioningHelper({ position: 'above', align: 'start', expectedPlacement: 'top-start' })
+ testPositioningHelper({ position: 'above', align: 'center', expectedPlacement: 'top' })
+ testPositioningHelper({ position: 'above', align: 'end', expectedPlacement: 'top-end' })
+ testPositioningHelper({ position: 'below', align: 'start', expectedPlacement: 'bottom-start' })
+ testPositioningHelper({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
+ testPositioningHelper({ position: 'below', align: 'end', expectedPlacement: 'bottom-end' })
+ testPositioningHelper({ position: 'before', align: 'top', expectedPlacement: 'left-start' })
+ testPositioningHelper({ position: 'before', align: 'center', expectedPlacement: 'left' })
+ testPositioningHelper({ position: 'before', align: 'bottom', expectedPlacement: 'left-end' })
+ testPositioningHelper({ position: 'after', align: 'top', expectedPlacement: 'right-start' })
+ testPositioningHelper({ position: 'after', align: 'center', expectedPlacement: 'right' })
+ testPositioningHelper({ position: 'after', align: 'bottom', expectedPlacement: 'right-end' })
+ })
+
+ describe('handles positioningHelper position argument correctly in rtl', () => {
+ testPositioningHelperInRtl({ position: 'above', align: 'start', expectedPlacement: 'top-end' })
+ testPositioningHelperInRtl({ position: 'above', align: 'center', expectedPlacement: 'top' })
+ testPositioningHelperInRtl({ position: 'above', align: 'end', expectedPlacement: 'top-start' })
+ testPositioningHelperInRtl({
+ position: 'below',
+ align: 'start',
+ expectedPlacement: 'bottom-end',
+ })
+ testPositioningHelperInRtl({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
+ testPositioningHelperInRtl({
+ position: 'below',
+ align: 'end',
+ expectedPlacement: 'bottom-start',
+ })
+ testPositioningHelperInRtl({
+ position: 'before',
+ align: 'top',
+ expectedPlacement: 'right-start',
+ })
+ testPositioningHelperInRtl({ position: 'before', align: 'center', expectedPlacement: 'right' })
+ testPositioningHelperInRtl({
+ position: 'before',
+ align: 'bottom',
+ expectedPlacement: 'right-end',
+ })
+ testPositioningHelperInRtl({ position: 'after', align: 'top', expectedPlacement: 'left-start' })
+ testPositioningHelperInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
+ testPositioningHelperInRtl({
+ position: 'after',
+ align: 'bottom',
+ expectedPlacement: 'left-end',
+ })
+ })
+
+ describe('positioningHelper offset argument transformed correctly in RTL', () => {
+ it("applies transform only for 'above' and 'below' postioning", () => {
+ const originalOffsetValue = '100%'
+
+ expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
+
+ expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
+ })
+
+ const expectOffsetTransformResult = (originalOffset, resultOffset) => {
+ expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
+ }
+
+ it('flips sign of simple expressions', () => {
+ expectOffsetTransformResult('100%', '-100%')
+ expectOffsetTransformResult(' 2000%p ', '-2000%p')
+ expectOffsetTransformResult('100 ', '-100')
+ expectOffsetTransformResult(' - 200vh', '200vh')
+ })
+
+ it('flips sign of complex expressions', () => {
+ expectOffsetTransformResult('100% + 200', '-100% - 200')
+ expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
+ })
+
+ it('transforms only horizontal offset value', () => {
+ const xOffset = '-100%'
+ const yOffset = '800vh'
+
+ const offsetValue = [xOffset, yOffset].join(',')
+ const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
+ ',',
+ )
+
+ expect(xOffsetTransformed.trim()).not.toBe(xOffset)
+ expect(yOffsetTransformed.trim()).toBe(yOffset)
+ })
+ })
+})
From e04a12466f470502013d53ab3dc87929f91ef9fc Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 9 May 2019 20:37:53 +0200
Subject: [PATCH 02/30] changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f800aa84e..c36ce8a99b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,6 +54,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `mountNode` and `mountDocument` props to allow proper multi-window rendering @layershifter ([#1288](https://github.com/stardust-ui/react/pull/1288))
- Added default and brand color schemes in Teams' theme @mnajdova ([#1069](https://github.com/stardust-ui/react/pull/1069))
- Export `files-upload` SVG icon for `Teams` theme @manindr ([#1293](https://github.com/stardust-ui/react/pull/1293))
+- Add `align`, `position`, `offset` props for `Dropdown` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
## [v0.29.1](https://github.com/stardust-ui/react/tree/v0.29.1) (2019-05-01)
From 9db8569eaad2fbaa7e5b399a479dc510619b5476 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Fri, 10 May 2019 13:56:39 +0200
Subject: [PATCH 03/30] addresed part of PR comments
---
packages/react-component-ref/src/Ref.tsx | 2 +-
packages/react/src/components/Dropdown/Dropdown.tsx | 2 +-
packages/react/src/lib/positioner/UpdatableComponent.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/react-component-ref/src/Ref.tsx b/packages/react-component-ref/src/Ref.tsx
index 27afbacfca..ee2face475 100644
--- a/packages/react-component-ref/src/Ref.tsx
+++ b/packages/react-component-ref/src/Ref.tsx
@@ -14,7 +14,7 @@ export interface RefProps {
*
* @param {HTMLElement} node - Referred node.
*/
- innerRef: React.Ref
+ innerRef: React.Ref
}
const Ref: React.FunctionComponent = props => {
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 8d48c18974..ca557d586c 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -247,7 +247,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
content: false,
}),
activeSelectedIndex: PropTypes.number,
- align: PropTypes.oneOf(_.without(ALIGNMENTS)),
+ align: PropTypes.oneOf(ALIGNMENTS),
clearable: PropTypes.bool,
clearIndicator: customPropTypes.itemShorthand,
defaultActiveSelectedIndex: PropTypes.number,
diff --git a/packages/react/src/lib/positioner/UpdatableComponent.tsx b/packages/react/src/lib/positioner/UpdatableComponent.tsx
index c53791c9f6..0d4180c5dc 100644
--- a/packages/react/src/lib/positioner/UpdatableComponent.tsx
+++ b/packages/react/src/lib/positioner/UpdatableComponent.tsx
@@ -30,7 +30,7 @@ interface UpdatableListProps {
const UpdatableComponent: React.FunctionComponent> = props => {
const { Component, innerRef, scheduleUpdate, updateDependencies, ...rest } = props
- React.useEffect(() => scheduleUpdate && scheduleUpdate(), updateDependencies)
+ React.useEffect(() => scheduleUpdate(), updateDependencies)
if (!innerRef) return
return (
From 36c337a214bff3632294f583ddd00e18636821eb Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Fri, 10 May 2019 18:01:54 +0200
Subject: [PATCH 04/30] - fixed dropdown toggle indicator icon direction; -
implemented knobs for dropdown and popup examples for position
---
.../DropdownExamplePosition.shorthand.tsx | 68 +++++----
.../PopupExamplePosition.shorthand.tsx | 127 +++++++++++------
.../Popup/Variations/PopupExamplePosition.tsx | 130 +++++++++++-------
.../src/knobs/useSelectKnob.ts | 6 +-
.../src/components/Dropdown/Dropdown.tsx | 11 +-
5 files changed, 214 insertions(+), 128 deletions(-)
diff --git a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
index fa68a71939..3fc4d19c0e 100644
--- a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
@@ -1,38 +1,48 @@
import * as React from 'react'
-import { Grid, Dropdown } from '@stardust-ui/react'
+import { Dropdown, Grid, Alignment, Position } from '@stardust-ui/react'
+import { useSelectKnob, useBooleanKnob } from '@stardust-ui/docs-components'
-const inputItems = ['Bruce Wayne', 'Natasha Romanoff', 'Steven Strange', 'Alfred Pennyworth']
+const inputItems = ['Bruce Wayne', 'Natasha Romanoff', 'Steven Strange']
-const DropdownArrowExample = props => {
- const { position, align } = props
+const DropdownExamplePosition = () => {
+ const [open] = useBooleanKnob({ name: 'dropdown open', initialValue: true })
+
+ const [position] = useSelectKnob({
+ name: 'position',
+ initialValue: 'below',
+ values: ['above', 'below', 'before', 'after'],
+ })
+
+ const [positionBeforeOrAfter, setPositionBeforeOrAfter] = React.useState(
+ isPositionBeforeOrAfter(position),
+ )
+
+ const [align] = useSelectKnob({
+ name: 'align N.A.',
+ ...(positionBeforeOrAfter && {
+ name: 'align',
+ initialValue: 'top',
+ values: ['top', 'bottom'],
+ }),
+ })
+
+ React.useEffect(() => setPositionBeforeOrAfter(isPositionBeforeOrAfter(position)), [position])
return (
-
+
+
+
)
}
-const triggers = [
- { position: 'above', align: 'start' },
- { position: 'below', align: 'start' },
- { position: 'above', align: 'end' },
- { position: 'below', align: 'end' },
- { position: 'after', align: 'top' },
- { position: 'before', align: 'top' },
- { position: 'after', align: 'bottom' },
- { position: 'before', align: 'bottom' },
-]
-
-const DropdownExamplePosition = () => (
-
- {triggers.map(({ position, align }) => (
-
- ))}
-
-)
-
export default DropdownExamplePosition
+
+const isPositionBeforeOrAfter = (position: Position) =>
+ position === 'before' || position === 'after'
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
index 29c10911e8..cceec68db8 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
@@ -1,54 +1,91 @@
import * as React from 'react'
-import { Button, Grid, Popup } from '@stardust-ui/react'
+import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
+import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
-const PopupWithButton = props => {
- const { position, align, icon, padding } = props
+const PopupExamplePosition = () => {
+ const [open] = useBooleanKnob({ name: 'open shorthand', initialValue: true })
- return (
- }
- content={{
- content: (
-
- The popup is rendered {position} the trigger
-
- aligned to the {align}.
-
- ),
- }}
- />
+ const [position] = useSelectKnob({
+ name: 'position shorthand',
+ initialValue: 'above',
+ values: ['above', 'below', 'before', 'after'],
+ })
+
+ const [positionBeforeOrAfter, setPositionBeforeOrAfter] = React.useState(
+ isPositionBeforeOrAfter(position),
)
-}
-const triggers = [
- { position: 'above', align: 'start', icon: 'arrow circle up', padding: '5px 42px 18px 5px' },
- { position: 'above', align: 'center', icon: 'arrow circle up', padding: '5px 5px 18px 5px' },
- { position: 'above', align: 'end', icon: 'arrow circle up', padding: '5px 5px 18px 42px' },
- { position: 'below', align: 'start', icon: 'arrow circle down', padding: '18px 42px 5px 5px' },
- { position: 'below', align: 'center', icon: 'arrow circle down', padding: '18px 5px 5px 5px' },
- { position: 'below', align: 'end', icon: 'arrow circle down', padding: '18px 5px 5px 42px' },
- { position: 'before', align: 'top', icon: 'arrow circle left', padding: '5px 42px 18px 5px' },
- { position: 'before', align: 'center', icon: 'arrow circle left', padding: '5px 42px 5px 5px' },
- { position: 'before', align: 'bottom', icon: 'arrow circle left', padding: '18px 42px 5px 5px' },
- { position: 'after', align: 'top', icon: 'arrow circle right', padding: '5px 5px 18px 42px' },
- { position: 'after', align: 'center', icon: 'arrow circle right', padding: '5px 5px 5px 42px' },
- { position: 'after', align: 'bottom', icon: 'arrow circle right', padding: '18px 5px 5px 42px' },
-]
-
-const PopupExamplePosition = () => (
-
- {triggers.map(({ position, align, icon, padding }) => (
- (
+ positionBeforeOrAfter
+ ? {
+ name: 'h-align shorthand',
+ initialValue: 'top',
+ values: ['top', 'center', 'bottom'],
+ }
+ : {
+ name: 'v-align shorthand',
+ initialValue: 'start',
+ values: ['start', 'center', 'end'],
+ },
+ )
+
+ React.useEffect(() => setPositionBeforeOrAfter(isPositionBeforeOrAfter(position)), [position])
+
+ const buttonStyles = { padding: paddings[position][align], height: '38px', minWidth: '64px' }
+
+ return (
+
+ }
+ content={{
+ content: (
+
+ The popup is rendered {position} the trigger
+
+ aligned to the {align}.
+
+ ),
+ }}
/>
- ))}
-
-)
+
+ )
+}
export default PopupExamplePosition
+
+const icons: { [key in Position]: string } = {
+ above: 'arrow circle up',
+ below: 'arrow circle down',
+ before: 'arrow circle left',
+ after: 'arrow circle right',
+}
+
+const paddings: { [key in Position]: { [key in Alignment]?: React.CSSProperties['padding'] } } = {
+ above: {
+ start: '5px 42px 18px 5px',
+ center: '5px 5px 18px 5px',
+ end: '5px 5px 18px 42px',
+ },
+ below: {
+ start: '18px 42px 5px 5px',
+ center: '18px 5px 5px 5px',
+ end: '18px 5px 5px 42px',
+ },
+ before: {
+ top: '5px 42px 18px 5px',
+ center: '5px 42px 5px 5px',
+ bottom: '18px 42px 5px 5px',
+ },
+ after: {
+ top: '5px 5px 18px 42px',
+ center: '5px 5px 5px 42px',
+ bottom: '18px 5px 5px 42px',
+ },
+}
+
+const isPositionBeforeOrAfter = (position: Position) =>
+ position === 'before' || position === 'after'
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
index bbce0f5ee8..cac546979f 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
@@ -1,57 +1,91 @@
import * as React from 'react'
-import { Button, Grid, Popup } from '@stardust-ui/react'
+import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
+import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
-const PopupArrowExample = props => {
- const { position, align, icon, padding } = props
+const PopupExamplePosition = () => {
+ const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const buttonStyles = { padding, height: '38px', minWidth: '64px' }
+ const [position] = useSelectKnob({
+ name: 'position',
+ initialValue: 'above',
+ values: ['above', 'below', 'before', 'after'],
+ })
- return (
-
- The popup is rendered {position} the trigger
-
- aligned to the {align}.
-
- ),
- }}
- >
-
-
+ const [positionBeforeOrAfter, setPositionBeforeOrAfter] = React.useState(
+ isPositionBeforeOrAfter(position),
)
-}
-const triggers = [
- { position: 'above', align: 'start', icon: 'arrow circle up', padding: '5px 42px 18px 5px' },
- { position: 'above', align: 'center', icon: 'arrow circle up', padding: '5px 5px 18px 5px' },
- { position: 'above', align: 'end', icon: 'arrow circle up', padding: '5px 5px 18px 42px' },
- { position: 'below', align: 'start', icon: 'arrow circle down', padding: '18px 42px 5px 5px' },
- { position: 'below', align: 'center', icon: 'arrow circle down', padding: '18px 5px 5px 5px' },
- { position: 'below', align: 'end', icon: 'arrow circle down', padding: '18px 5px 5px 42px' },
- { position: 'before', align: 'top', icon: 'arrow circle left', padding: '5px 42px 18px 5px' },
- { position: 'before', align: 'center', icon: 'arrow circle left', padding: '5px 42px 5px 5px' },
- { position: 'before', align: 'bottom', icon: 'arrow circle left', padding: '18px 42px 5px 5px' },
- { position: 'after', align: 'top', icon: 'arrow circle right', padding: '5px 5px 18px 42px' },
- { position: 'after', align: 'center', icon: 'arrow circle right', padding: '5px 5px 5px 42px' },
- { position: 'after', align: 'bottom', icon: 'arrow circle right', padding: '18px 5px 5px 42px' },
-]
-
-const PopupExamplePosition = () => (
-
- {triggers.map(({ position, align, icon, padding }) => (
- (
+ positionBeforeOrAfter
+ ? {
+ name: 'h-align',
+ initialValue: 'top',
+ values: ['top', 'center', 'bottom'],
+ }
+ : {
+ name: 'v-align',
+ initialValue: 'start',
+ values: ['start', 'center', 'end'],
+ },
+ )
+
+ React.useEffect(() => setPositionBeforeOrAfter(isPositionBeforeOrAfter(position)), [position])
+
+ const buttonStyles = { padding: paddings[position][align], height: '38px', minWidth: '64px' }
+
+ return (
+
+
- ))}
-
-)
+ position={position}
+ content={{
+ content: (
+
+ The popup is rendered {position} the trigger
+
+ aligned to the {align}.
+
+ ),
+ }}
+ >
+
+
+
+ )
+}
export default PopupExamplePosition
+
+const icons: { [key in Position]: string } = {
+ above: 'arrow circle up',
+ below: 'arrow circle down',
+ before: 'arrow circle left',
+ after: 'arrow circle right',
+}
+
+const paddings: { [key in Position]: { [key in Alignment]?: React.CSSProperties['padding'] } } = {
+ above: {
+ start: '5px 42px 18px 5px',
+ center: '5px 5px 18px 5px',
+ end: '5px 5px 18px 42px',
+ },
+ below: {
+ start: '18px 42px 5px 5px',
+ center: '18px 5px 5px 5px',
+ end: '18px 5px 5px 42px',
+ },
+ before: {
+ top: '5px 42px 18px 5px',
+ center: '5px 42px 5px 5px',
+ bottom: '18px 42px 5px 5px',
+ },
+ after: {
+ top: '5px 5px 18px 42px',
+ center: '5px 5px 5px 42px',
+ bottom: '18px 5px 5px 42px',
+ },
+}
+
+const isPositionBeforeOrAfter = (position: Position) =>
+ position === 'before' || position === 'after'
diff --git a/packages/docs-components/src/knobs/useSelectKnob.ts b/packages/docs-components/src/knobs/useSelectKnob.ts
index 4caf07ab60..a8d4d0a95d 100644
--- a/packages/docs-components/src/knobs/useSelectKnob.ts
+++ b/packages/docs-components/src/knobs/useSelectKnob.ts
@@ -2,10 +2,6 @@ import { UseKnobOptions } from './types'
import useKnob from './useKnob'
const useSelectKnob = (options: UseKnobOptions) =>
- useKnob({
- initialValue: '' as T,
- type: 'select',
- ...options,
- })
+ useKnob({ initialValue: '' as T, type: 'select', ...options })
export default useSelectKnob
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index ca557d586c..37f5c0940a 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -475,7 +475,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
: Icon.create(toggleIndicator, {
defaultProps: {
className: Dropdown.slotClassNames.toggleIndicator,
- name: open ? 'stardust-arrow-up' : 'stardust-arrow-down',
+ name: this.getToggleIndicatorIconName(),
styles: styles.toggleIndicator,
},
overrideProps: (predefinedProps: IconProps) => ({
@@ -1315,6 +1315,15 @@ class Dropdown extends AutoControlledComponent, Dropdo
this.setState({ startingString: '' })
}, Dropdown.charKeyPressedCleanupTime)
}
+
+ private getToggleIndicatorIconName = (): string => {
+ const { position, open } = this.props
+ const positionedAbove = position === 'above'
+
+ return (open && !positionedAbove) || (!open && positionedAbove)
+ ? 'stardust-arrow-up'
+ : 'stardust-arrow-down'
+ }
}
Dropdown.slotClassNames = {
From e4c33b65386df18fc276caf71c4c3076b6f8c955 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Fri, 10 May 2019 18:02:56 +0200
Subject: [PATCH 05/30] changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c36ce8a99b..6ddbbbd376 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add styles for the dark and high contrast Teams themes for the `Dropdown` component @mnajdova ([#1299](https://github.com/stardust-ui/react/pull/1299))
- Highlight options by character keys in `Dropdown` non-search versions @silviuavram ([#1270](https://github.com/stardust-ui/react/pull/1270))
- Aligned link styles for `Chat.Message` component with latest Teams theme design @Bugaa92 ([#1269](https://github.com/stardust-ui/react/pull/1269))
+- Add `align`, `position`, `offset` props for `Dropdown` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
## [v0.30.0](https://github.com/stardust-ui/react/tree/v0.30.0) (2019-05-10)
@@ -54,7 +55,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `mountNode` and `mountDocument` props to allow proper multi-window rendering @layershifter ([#1288](https://github.com/stardust-ui/react/pull/1288))
- Added default and brand color schemes in Teams' theme @mnajdova ([#1069](https://github.com/stardust-ui/react/pull/1069))
- Export `files-upload` SVG icon for `Teams` theme @manindr ([#1293](https://github.com/stardust-ui/react/pull/1293))
-- Add `align`, `position`, `offset` props for `Dropdown` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
## [v0.29.1](https://github.com/stardust-ui/react/tree/v0.29.1) (2019-05-01)
From a9c5b0588ce8754b3307f88a03c156439d371423 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Fri, 10 May 2019 18:45:55 +0200
Subject: [PATCH 06/30] - refactored Positioner to include updating logic; -
removed UpdatableComponent
---
.../src/components/Dropdown/Dropdown.tsx | 83 +++++++------------
packages/react/src/components/Popup/Popup.tsx | 9 +-
.../react/src/lib/positioner/Positioner.tsx | 24 +++++-
.../src/lib/positioner/UpdatableComponent.tsx | 43 ----------
packages/react/src/lib/positioner/index.ts | 1 -
5 files changed, 53 insertions(+), 107 deletions(-)
delete mode 100644 packages/react/src/lib/positioner/UpdatableComponent.tsx
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 37f5c0940a..17c4f7bf2e 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -32,7 +32,7 @@ import {
UIComponentProps,
isFromKeyboard,
} from '../../lib'
-import List, { ListProps } from '../List/List'
+import List from '../List/List'
import DropdownItem, { DropdownItemProps } from './DropdownItem'
import DropdownSelectedItem, { DropdownSelectedItemProps } from './DropdownSelectedItem'
import DropdownSearchInput, { DropdownSearchInputProps } from './DropdownSearchInput'
@@ -41,13 +41,7 @@ import { screenReaderContainerStyles } from '../../lib/accessibility/Styles/acce
import ListItem from '../List/ListItem'
import Icon, { IconProps } from '../Icon/Icon'
import Portal from '../Portal/Portal'
-import {
- ALIGNMENTS,
- POSITIONS,
- Positioner,
- PositionCommonProps,
- UpdatableComponent,
-} from '../../lib/positioner'
+import { ALIGNMENTS, POSITIONS, Positioner, PositionCommonProps } from '../../lib/positioner'
export interface DropdownSlotClassNames {
clearIndicator: string
@@ -485,7 +479,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
},
}),
})}
- {this.preparePropsAndRenderItemsList(
+ {this.renderItemsList(
styles,
variables,
highlightedIndex,
@@ -606,7 +600,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
})
}
- private preparePropsAndRenderItemsList(
+ private renderItemsList(
styles: ComponentSlotStylesInput,
variables: ComponentVariablesInput,
highlightedIndex: number,
@@ -618,8 +612,11 @@ class Dropdown extends AutoControlledComponent, Dropdo
value: ShorthandValue | ShorthandCollection,
rtl: boolean,
) {
- const { search } = this.props
+ const { align, offset, position, search } = this.props
const { open } = this.state
+ const items = open
+ ? this.renderItems(styles, variables, getItemProps, highlightedIndex, value)
+ : []
const { innerRef, ...accessibilityMenuProps } = getMenuProps(
{ refKey: 'innerRef' },
{ suppressRefError: true },
@@ -649,49 +646,31 @@ class Dropdown extends AutoControlledComponent, Dropdo
handleRef(innerRef, listElement)
}}
>
- {this.renderItemsList(
- {
- className: Dropdown.slotClassNames.itemsList,
- ...accessibilityMenuProps,
- styles: styles.list,
- tabIndex: search ? undefined : -1, // needs to be focused when trigger button is activated.
- 'aria-hidden': !open,
- onFocus: this.handleTriggerButtonOrListFocus,
- onBlur: this.handleListBlur,
- items: open
- ? this.renderItems(styles, variables, getItemProps, highlightedIndex, value)
- : [],
- },
- rtl,
- )}
+ (
+
+ )}
+ />
)
}
- private renderItemsList(listProps: ListProps, rtl: boolean): JSX.Element {
- const { align, position, offset } = this.props
-
- return (
- (
-
- )}
- />
- )
- }
-
private renderItems(
styles: ComponentSlotStylesInput,
variables: ComponentVariablesInput,
@@ -1317,8 +1296,8 @@ class Dropdown extends AutoControlledComponent, Dropdo
}
private getToggleIndicatorIconName = (): string => {
- const { position, open } = this.props
- const positionedAbove = position === 'above'
+ const { open } = this.state
+ const positionedAbove = this.props.position === 'above'
return (open && !positionedAbove) || (!open && positionedAbove)
? 'stardust-arrow-up'
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index 0722c77142..7bb469ffac 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -402,13 +402,7 @@ export default class Popup extends AutoControlledComponent {
const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
const content = renderContent ? renderContent(scheduleUpdate) : propsContent
@@ -457,7 +451,6 @@ export default class Popup extends AutoControlledComponent
[ {
- ref(domElement)
this.popupDomElement = domElement
handleRef(contentRef, domElement)
handleRef(nestingRef, domElement)
diff --git a/packages/react/src/lib/positioner/Positioner.tsx b/packages/react/src/lib/positioner/Positioner.tsx
index f0577bf4e1..dfc05da180 100644
--- a/packages/react/src/lib/positioner/Positioner.tsx
+++ b/packages/react/src/lib/positioner/Positioner.tsx
@@ -1,5 +1,6 @@
import * as React from 'react'
import { Popper, PopperChildrenProps, PopperProps } from 'react-popper'
+import { Ref } from '@stardust-ui/react-component-ref'
import { Modifiers } from 'popper.js'
import { Alignment, Position } from './index'
@@ -29,7 +30,7 @@ export interface PositionCommonProps {
interface PositionerProps extends PopperProps, PositionCommonProps {
/**
- * Content for children using render props API
+ * Content for children using render props API.
*/
children: (props: PopperChildrenProps) => React.ReactNode
@@ -38,24 +39,41 @@ interface PositionerProps extends PopperProps, PositionCommonProps {
*/
rtl?: boolean
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
+ */
+ positioningDependencies?: any[]
+
+ /**
+ * DOM element or ref representing the target used by the positioning mechanism.
+ */
target?: HTMLElement | React.RefObject
}
const Positioner: React.FunctionComponent = props => {
- const { align, children, position, offset, rtl, target, ...rest } = props
+ const { align, children, offset, position, positioningDependencies, rtl, target, ...rest } = props
// https://popper.js.org/popper-documentation.html#modifiers..offset
const popperModifiers: Modifiers = offset && {
offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
keepTogether: { enabled: false },
}
+ const scheduleUpdate = React.useRef(null)
+
+ React.useEffect(() => {
+ if (scheduleUpdate.current) scheduleUpdate.current()
+ }, positioningDependencies)
+
return (
{
+ scheduleUpdate.current = props.scheduleUpdate
+ return ][{children(props) as React.ReactElement}]
+ }}
{...rest}
/>
)
diff --git a/packages/react/src/lib/positioner/UpdatableComponent.tsx b/packages/react/src/lib/positioner/UpdatableComponent.tsx
deleted file mode 100644
index 0d4180c5dc..0000000000
--- a/packages/react/src/lib/positioner/UpdatableComponent.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as React from 'react'
-import { Ref } from '@stardust-ui/react-component-ref'
-
-import { Extendable } from '../../types'
-
-interface UpdatableListProps {
- /**
- * Component that will be rendered.
- */
- Component: React.ComponentType
-
- /**
- * Called when a child component will be mounted or updated.
- *
- * @param {HTMLElement} node - Referred node.
- */
- innerRef?: React.Ref
-
- /**
- * Function that will trigger the rerender.
- */
- scheduleUpdate: Function
-
- /**
- * Array of conditions to be met in order to trigger a subsequent render.
- */
- updateDependencies: any[]
-}
-
-const UpdatableComponent: React.FunctionComponent> = props => {
- const { Component, innerRef, scheduleUpdate, updateDependencies, ...rest } = props
-
- React.useEffect(() => scheduleUpdate(), updateDependencies)
-
- if (!innerRef) return
- return (
- [
-
- ]
- )
-}
-
-export default UpdatableComponent
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
index a538bcac98..3654ff28db 100644
--- a/packages/react/src/lib/positioner/index.ts
+++ b/packages/react/src/lib/positioner/index.ts
@@ -4,4 +4,3 @@ export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
export { default as Positioner, PositionCommonProps } from './Positioner'
-export { default as UpdatableComponent } from './UpdatableComponent'
From 69cc42539fb7e991280451d670b1b90ae8c55f90 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Fri, 10 May 2019 18:49:26 +0200
Subject: [PATCH 07/30] removed redundant prop
---
packages/react/src/lib/positioner/Positioner.tsx | 5 -----
1 file changed, 5 deletions(-)
diff --git a/packages/react/src/lib/positioner/Positioner.tsx b/packages/react/src/lib/positioner/Positioner.tsx
index dfc05da180..ae5aef5788 100644
--- a/packages/react/src/lib/positioner/Positioner.tsx
+++ b/packages/react/src/lib/positioner/Positioner.tsx
@@ -29,11 +29,6 @@ export interface PositionCommonProps {
}
interface PositionerProps extends PopperProps, PositionCommonProps {
- /**
- * Content for children using render props API.
- */
- children: (props: PopperChildrenProps) => React.ReactNode
-
/**
* rtl attribute for the component
*/
From 552df58a0e559c1050d1703b227d00a9537546d3 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 13 May 2019 19:08:54 +0200
Subject: [PATCH 08/30] addressed PR comments
---
.../DropdownExamplePosition.shorthand.tsx | 44 ++++----
.../PopupExamplePosition.shorthand.tsx | 102 +++++++++---------
.../Popup/Variations/PopupExamplePosition.tsx | 100 ++++++++---------
.../src/knobs/KnobProvider.tsx | 13 +--
4 files changed, 131 insertions(+), 128 deletions(-)
diff --git a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
index 3fc4d19c0e..a69834284c 100644
--- a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
@@ -1,38 +1,26 @@
import * as React from 'react'
+import * as _ from 'lodash'
import { Dropdown, Grid, Alignment, Position } from '@stardust-ui/react'
import { useSelectKnob, useBooleanKnob } from '@stardust-ui/docs-components'
const inputItems = ['Bruce Wayne', 'Natasha Romanoff', 'Steven Strange']
const DropdownExamplePosition = () => {
- const [open] = useBooleanKnob({ name: 'dropdown open', initialValue: true })
+ const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const [position] = useSelectKnob({
- name: 'position',
+ const [positionAndAlign] = useSelectKnob({
+ name: 'position-align',
initialValue: 'below',
- values: ['above', 'below', 'before', 'after'],
+ values: positionAndAlignValues,
})
- const [positionBeforeOrAfter, setPositionBeforeOrAfter] = React.useState(
- isPositionBeforeOrAfter(position),
- )
-
- const [align] = useSelectKnob({
- name: 'align N.A.',
- ...(positionBeforeOrAfter && {
- name: 'align',
- initialValue: 'top',
- values: ['top', 'bottom'],
- }),
- })
-
- React.useEffect(() => setPositionBeforeOrAfter(isPositionBeforeOrAfter(position)), [position])
+ const [position, align] = _.split(positionAndAlign, '-') as [Position, Alignment]
return (
{
export default DropdownExamplePosition
-const isPositionBeforeOrAfter = (position: Position) =>
- position === 'before' || position === 'after'
+type PositionAndAlign =
+ | 'above'
+ | 'below'
+ | 'before-top'
+ | 'before-bottom'
+ | 'after-top'
+ | 'after-bottom'
+
+const positionAndAlignValues: PositionAndAlign[] = [
+ 'above',
+ 'below',
+ 'before-top',
+ 'before-bottom',
+ 'after-top',
+ 'after-bottom',
+]
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
index cceec68db8..8cc6d269b1 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
@@ -1,43 +1,25 @@
import * as React from 'react'
+import * as _ from 'lodash'
import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
const PopupExamplePosition = () => {
- const [open] = useBooleanKnob({ name: 'open shorthand', initialValue: true })
+ const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const [position] = useSelectKnob({
- name: 'position shorthand',
- initialValue: 'above',
- values: ['above', 'below', 'before', 'after'],
+ const [positionAndAlign] = useSelectKnob({
+ name: 'position-align',
+ initialValue: 'above-start',
+ values: positionAndAlignValues,
})
- const [positionBeforeOrAfter, setPositionBeforeOrAfter] = React.useState(
- isPositionBeforeOrAfter(position),
- )
-
- const [align] = useSelectKnob(
- positionBeforeOrAfter
- ? {
- name: 'h-align shorthand',
- initialValue: 'top',
- values: ['top', 'center', 'bottom'],
- }
- : {
- name: 'v-align shorthand',
- initialValue: 'start',
- values: ['start', 'center', 'end'],
- },
- )
-
- React.useEffect(() => setPositionBeforeOrAfter(isPositionBeforeOrAfter(position)), [position])
-
- const buttonStyles = { padding: paddings[position][align], height: '38px', minWidth: '64px' }
+ const [position, align] = _.split(positionAndAlign, '-') as [Position, Alignment]
+ const buttonStyles = { padding: paddings[positionAndAlign], height: '38px', minWidth: '64px' }
return (
}
@@ -57,6 +39,35 @@ const PopupExamplePosition = () => {
export default PopupExamplePosition
+type PositionAndAlign =
+ | 'above-start'
+ | 'above-center'
+ | 'above-end'
+ | 'below-start'
+ | 'below-center'
+ | 'below-end'
+ | 'before-top'
+ | 'before-center'
+ | 'before-bottom'
+ | 'after-top'
+ | 'after-center'
+ | 'after-bottom'
+
+const positionAndAlignValues: PositionAndAlign[] = [
+ 'above-start',
+ 'above-center',
+ 'above-end',
+ 'below-start',
+ 'below-center',
+ 'below-end',
+ 'before-top',
+ 'before-center',
+ 'before-bottom',
+ 'after-top',
+ 'after-center',
+ 'after-bottom',
+]
+
const icons: { [key in Position]: string } = {
above: 'arrow circle up',
below: 'arrow circle down',
@@ -64,28 +75,17 @@ const icons: { [key in Position]: string } = {
after: 'arrow circle right',
}
-const paddings: { [key in Position]: { [key in Alignment]?: React.CSSProperties['padding'] } } = {
- above: {
- start: '5px 42px 18px 5px',
- center: '5px 5px 18px 5px',
- end: '5px 5px 18px 42px',
- },
- below: {
- start: '18px 42px 5px 5px',
- center: '18px 5px 5px 5px',
- end: '18px 5px 5px 42px',
- },
- before: {
- top: '5px 42px 18px 5px',
- center: '5px 42px 5px 5px',
- bottom: '18px 42px 5px 5px',
- },
- after: {
- top: '5px 5px 18px 42px',
- center: '5px 5px 5px 42px',
- bottom: '18px 5px 5px 42px',
- },
+const paddings: Record = {
+ 'above-start': '5px 42px 18px 5px',
+ 'above-center': '5px 5px 18px 5px',
+ 'above-end': '5px 5px 18px 42px',
+ 'below-start': '18px 42px 5px 5px',
+ 'below-center': '18px 5px 5px 5px',
+ 'below-end': '18px 5px 5px 42px',
+ 'before-top': '5px 42px 18px 5px',
+ 'before-center': '5px 42px 5px 5px',
+ 'before-bottom': '18px 42px 5px 5px',
+ 'after-top': '5px 5px 18px 42px',
+ 'after-center': '5px 5px 5px 42px',
+ 'after-bottom': '18px 5px 5px 42px',
}
-
-const isPositionBeforeOrAfter = (position: Position) =>
- position === 'before' || position === 'after'
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
index cac546979f..26585cf47e 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
@@ -1,42 +1,24 @@
import * as React from 'react'
+import * as _ from 'lodash'
import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
const PopupExamplePosition = () => {
const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const [position] = useSelectKnob({
- name: 'position',
- initialValue: 'above',
- values: ['above', 'below', 'before', 'after'],
+ const [positionAndAlign] = useSelectKnob({
+ name: 'position-align',
+ initialValue: 'above-start',
+ values: positionAndAlignValues,
})
- const [positionBeforeOrAfter, setPositionBeforeOrAfter] = React.useState(
- isPositionBeforeOrAfter(position),
- )
-
- const [align] = useSelectKnob(
- positionBeforeOrAfter
- ? {
- name: 'h-align',
- initialValue: 'top',
- values: ['top', 'center', 'bottom'],
- }
- : {
- name: 'v-align',
- initialValue: 'start',
- values: ['start', 'center', 'end'],
- },
- )
-
- React.useEffect(() => setPositionBeforeOrAfter(isPositionBeforeOrAfter(position)), [position])
-
- const buttonStyles = { padding: paddings[position][align], height: '38px', minWidth: '64px' }
+ const [position, align] = _.split(positionAndAlign, '-') as [Position, Alignment]
+ const buttonStyles = { padding: paddings[positionAndAlign], height: '38px', minWidth: '64px' }
return (
{
export default PopupExamplePosition
+type PositionAndAlign =
+ | 'above-start'
+ | 'above-center'
+ | 'above-end'
+ | 'below-start'
+ | 'below-center'
+ | 'below-end'
+ | 'before-top'
+ | 'before-center'
+ | 'before-bottom'
+ | 'after-top'
+ | 'after-center'
+ | 'after-bottom'
+
+const positionAndAlignValues: PositionAndAlign[] = [
+ 'above-start',
+ 'above-center',
+ 'above-end',
+ 'below-start',
+ 'below-center',
+ 'below-end',
+ 'before-top',
+ 'before-center',
+ 'before-bottom',
+ 'after-top',
+ 'after-center',
+ 'after-bottom',
+]
+
const icons: { [key in Position]: string } = {
above: 'arrow circle up',
below: 'arrow circle down',
@@ -64,28 +75,17 @@ const icons: { [key in Position]: string } = {
after: 'arrow circle right',
}
-const paddings: { [key in Position]: { [key in Alignment]?: React.CSSProperties['padding'] } } = {
- above: {
- start: '5px 42px 18px 5px',
- center: '5px 5px 18px 5px',
- end: '5px 5px 18px 42px',
- },
- below: {
- start: '18px 42px 5px 5px',
- center: '18px 5px 5px 5px',
- end: '18px 5px 5px 42px',
- },
- before: {
- top: '5px 42px 18px 5px',
- center: '5px 42px 5px 5px',
- bottom: '18px 42px 5px 5px',
- },
- after: {
- top: '5px 5px 18px 42px',
- center: '5px 5px 5px 42px',
- bottom: '18px 5px 5px 42px',
- },
+const paddings: Record = {
+ 'above-start': '5px 42px 18px 5px',
+ 'above-center': '5px 5px 18px 5px',
+ 'above-end': '5px 5px 18px 42px',
+ 'below-start': '18px 42px 5px 5px',
+ 'below-center': '18px 5px 5px 5px',
+ 'below-end': '18px 5px 5px 42px',
+ 'before-top': '5px 42px 18px 5px',
+ 'before-center': '5px 42px 5px 5px',
+ 'before-bottom': '18px 42px 5px 5px',
+ 'after-top': '5px 5px 18px 42px',
+ 'after-center': '5px 5px 5px 42px',
+ 'after-bottom': '18px 5px 5px 42px',
}
-
-const isPositionBeforeOrAfter = (position: Position) =>
- position === 'before' || position === 'after'
diff --git a/packages/docs-components/src/knobs/KnobProvider.tsx b/packages/docs-components/src/knobs/KnobProvider.tsx
index 4e3b076016..f77a144e45 100644
--- a/packages/docs-components/src/knobs/KnobProvider.tsx
+++ b/packages/docs-components/src/knobs/KnobProvider.tsx
@@ -12,13 +12,14 @@ const KnobProvider: React.FunctionComponent = props => {
const [knobs, setKnobs] = React.useState({})
const registerKnob = (knob: KnobDefinition) => {
- if (process.env.NODE_ENV !== 'production') {
- if (knobs[knob.name]) {
- throw new Error(`Knob with name "${knob.name}" has been already registered`)
+ setKnobs(prevKnobs => {
+ if (process.env.NODE_ENV !== 'production') {
+ if (prevKnobs[knob.name]) {
+ throw new Error(`Knob with name "${knob.name}" has been already registered`)
+ }
}
- }
-
- setKnobs(prevKnob => ({ ...prevKnob, [knob.name]: knob }))
+ return { ...prevKnobs, [knob.name]: knob }
+ })
}
const setKnobValue = (knobName: KnobName, knobValue: any) => {
setKnobs(prevKnob => ({
From a0aa3b7271964cc511c1ba719622f4b22f69d525 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 13 May 2019 21:39:36 +0200
Subject: [PATCH 09/30] several fixes and improved perf
---
.../DropdownExamplePosition.shorthand.tsx | 2 +-
.../src/components/Dropdown/Dropdown.tsx | 1 +
.../react/src/lib/positioner/Positioner.tsx | 22 +++++++++++++++----
3 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
index a69834284c..cab6ba3a58 100644
--- a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
@@ -20,7 +20,7 @@ const DropdownExamplePosition = () => {
, Dropdo
position={position}
offset={offset}
rtl={rtl}
+ eventsEnabled={open}
target={this.selectedItemsRef}
positioningDependencies={[items.length]}
children={popperChildrenProps => (
diff --git a/packages/react/src/lib/positioner/Positioner.tsx b/packages/react/src/lib/positioner/Positioner.tsx
index ae5aef5788..0601333a45 100644
--- a/packages/react/src/lib/positioner/Positioner.tsx
+++ b/packages/react/src/lib/positioner/Positioner.tsx
@@ -46,11 +46,25 @@ interface PositionerProps extends PopperProps, PositionCommonProps {
}
const Positioner: React.FunctionComponent = props => {
- const { align, children, offset, position, positioningDependencies, rtl, target, ...rest } = props
+ const {
+ align,
+ children,
+ modifiers,
+ offset,
+ position,
+ positioningDependencies,
+ rtl,
+ target,
+ ...rest
+ } = props
+
// https://popper.js.org/popper-documentation.html#modifiers..offset
- const popperModifiers: Modifiers = offset && {
- offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
- keepTogether: { enabled: false },
+ const popperModifiers: Modifiers = {
+ ...(offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }),
+ ...modifiers,
}
const scheduleUpdate = React.useRef(null)
From d51cedbb25c554d1cd4bf55b280fbccf7a35e4d1 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 13 May 2019 21:44:13 +0200
Subject: [PATCH 10/30] add offset to proptypes for Popup and Dropdown
---
packages/react/src/components/Dropdown/Dropdown.tsx | 1 +
packages/react/src/components/Popup/Popup.tsx | 1 +
2 files changed, 2 insertions(+)
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 715ced5627..9b5f2427cf 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -265,6 +265,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
moveFocusOnTab: PropTypes.bool,
multiple: PropTypes.bool,
noResultsMessage: customPropTypes.itemShorthand,
+ offset: PropTypes.string,
onOpenChange: PropTypes.func,
onSearchQueryChange: PropTypes.func,
onSelectedChange: PropTypes.func,
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index 7bb469ffac..be7b742a08 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -145,6 +145,7 @@ export default class Popup extends AutoControlledComponent
Date: Wed, 15 May 2019 16:23:27 +0200
Subject: [PATCH 11/30] reimplement Popper from react-popper with our custom
component
---
.../src/components/Dropdown/Dropdown.tsx | 30 ++--
packages/react/src/components/Popup/Popup.tsx | 15 +-
.../src/components/Popup/PopupContent.tsx | 21 +--
packages/react/src/lib/positioner/Popper.tsx | 136 ++++++++++++++++++
.../react/src/lib/positioner/Positioner.tsx | 4 +-
packages/react/src/lib/positioner/index.ts | 1 +
6 files changed, 170 insertions(+), 37 deletions(-)
create mode 100644 packages/react/src/lib/positioner/Popper.tsx
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 9b5f2427cf..b888a3cdcb 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -640,6 +640,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
}
}
+ console.log('[Dropdown.renderItemsList')
return (
[ {
@@ -655,19 +656,22 @@ class Dropdown extends AutoControlledComponent, Dropdo
eventsEnabled={open}
target={this.selectedItemsRef}
positioningDependencies={[items.length]}
- children={popperChildrenProps => (
- ]
- )}
+ children={popperChildrenProps => {
+ console.log('[Dropdown.Positioner.children')
+ return (
+
+ )
+ }}
/>
)
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index be7b742a08..9bf66aa3ec 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -24,7 +24,7 @@ import {
setWhatInputSource,
} from '../../lib'
import { ComponentEventHandler, ShorthandValue } from '../../types'
-import { ALIGNMENTS, POSITIONS, Positioner, PositionCommonProps } from '../../lib/positioner'
+import { ALIGNMENTS, POSITIONS, Popper, PositionCommonProps } from '../../lib/positioner'
import PopupContent from './PopupContent'
import { popupBehavior } from '../../lib/accessibility'
import {
@@ -173,6 +173,7 @@ export default class Popup extends AutoControlledComponent()
triggerRef = React.createRef() as React.MutableRefObject
// focusable element which has triggered Popup, can be either triggerDomElement or the element inside it
triggerFocusableDomElement = null
@@ -387,12 +388,13 @@ export default class Popup extends AutoControlledComponent
)
@@ -403,8 +405,9 @@ export default class Popup extends AutoControlledComponent {
+ console.log('[Popup.renderPopperChildren]')
const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
const content = renderContent ? renderContent(scheduleUpdate) : propsContent
const documentRef = toRefObject(mountDocument)
@@ -414,7 +417,6 @@ export default class Popup extends AutoControlledComponent, any> {
@@ -69,8 +67,7 @@ class PopupContent extends UIComponent, any> {
pointing: PropTypes.bool,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
- pointerRef: PropTypes.func,
- pointerStyle: PropTypes.object,
+ pointerRef: customPropTypes.ref,
}
static defaultProps = {
@@ -92,7 +89,7 @@ class PopupContent extends UIComponent, any> {
unhandledProps,
styles,
}: RenderResultConfig): React.ReactNode {
- const { children, content, pointing, pointerRef, pointerStyle } = this.props
+ const { children, content, pointing, pointerRef } = this.props
return (
, any> {
>
{pointing && (
[
- {Box.create(
- {},
- {
- defaultProps: {
- style: pointerStyle,
- styles: styles.pointer,
- },
- },
- )}
+ {Box.create({}, { defaultProps: { styles: styles.pointer } })}
]
)}
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
new file mode 100644
index 0000000000..745c250ffb
--- /dev/null
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -0,0 +1,136 @@
+import * as React from 'react'
+import PopperJS from 'popper.js'
+import { Ref } from '@stardust-ui/react-component-ref'
+
+import { getPlacement, applyRtlToOffset } from './positioningHelper'
+import { Alignment, Position } from './index'
+
+/**
+ * Poppers rely on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning.
+ */
+const Popper: React.FunctionComponent = props => {
+ const {
+ align,
+ arrowRef,
+ children,
+ eventsEnabled,
+ offset,
+ // placement: userPlacement,
+ position,
+ positionFixed,
+ modifiers,
+ rtl,
+ targetRef,
+ // positioningDependencies,
+ } = props
+
+ const [placement, setPlacement] = React.useState(
+ getPlacement({ align, position, rtl }),
+ )
+ const popperRef = React.useRef()
+ const contentRef = React.useRef(null)
+
+ const handleUpdate = React.useCallback((data: PopperJS.Data) => {
+ if (data.placement !== placement) {
+ console.log('handleUpdate old placement: ', placement)
+ console.log('handleUpdate new placement: ', data.placement)
+ setPlacement(data.placement)
+ }
+ }, [])
+
+ React.useEffect(() => {
+ const options: PopperJS.PopperOptions = {
+ placement,
+ eventsEnabled,
+ positionFixed,
+ modifiers: {
+ ...(offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }),
+ ...modifiers,
+ arrow: {
+ enabled: !!arrowRef.current,
+ element: arrowRef.current,
+ },
+ },
+ onCreate: handleUpdate,
+ onUpdate: handleUpdate,
+ }
+
+ popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
+
+ return () => popperRef.current.destroy()
+ })
+
+ React.useEffect(
+ () => {
+ popperRef.current.scheduleUpdate()
+ },
+ [placement],
+ )
+
+ return (
+ [
+ {
+ children({
+ placement,
+ scheduleUpdate: () => {},
+ }) as React.ReactElement
+ }
+ ]
+ )
+}
+
+Popper.defaultProps = {
+ eventsEnabled: true,
+ targetRef: undefined,
+ positionFixed: false,
+}
+
+export default Popper
+
+interface PopperChildrenProps {
+ placement: PopperJS.Placement
+ scheduleUpdate: () => void
+}
+
+export interface PositionCommonProps {
+ /** Alignment for the component. */
+ align?: Alignment
+
+ /** Offset value to apply to rendered component. 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 component element
+ * - vw, CSS viewport width unit
+ * - vh, CSS viewport height unit
+ */
+ offset?: string
+
+ /**
+ * Position for the component. 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
+}
+
+interface PopperProps extends PositionCommonProps {
+ arrowRef?: React.RefObject
+ children: (props: PopperChildrenProps) => React.ReactNode
+ eventsEnabled?: boolean
+ modifiers?: PopperJS.Modifiers
+ positionFixed?: boolean
+ targetRef?: React.RefObject
+
+ /**
+ * rtl attribute for the component
+ */
+ rtl?: boolean
+
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
+ */
+ positioningDependencies?: any[]
+}
diff --git a/packages/react/src/lib/positioner/Positioner.tsx b/packages/react/src/lib/positioner/Positioner.tsx
index 0601333a45..b0e107d58e 100644
--- a/packages/react/src/lib/positioner/Positioner.tsx
+++ b/packages/react/src/lib/positioner/Positioner.tsx
@@ -68,18 +68,20 @@ const Positioner: React.FunctionComponent = props => {
}
const scheduleUpdate = React.useRef(null)
+ // const currentStyle = React.useRef(null)
React.useEffect(() => {
if (scheduleUpdate.current) scheduleUpdate.current()
}, positioningDependencies)
+ console.log('[Positioner.render]')
return (
{
+ console.log('[Positioner.children]')
scheduleUpdate.current = props.scheduleUpdate
return [{children(props) as React.ReactElement}]
}}
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
index 3654ff28db..b8760bc8c9 100644
--- a/packages/react/src/lib/positioner/index.ts
+++ b/packages/react/src/lib/positioner/index.ts
@@ -4,3 +4,4 @@ export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
export { default as Positioner, PositionCommonProps } from './Positioner'
+export { default as Popper } from './Popper'
From 27ea8317a7086ee27972167adea4c68f2b3a29d1 Mon Sep 17 00:00:00 2001
From: Oleksandr Fediashov
Date: Wed, 15 May 2019 17:06:30 +0200
Subject: [PATCH 12/30] fix positioning
---
packages/react/src/lib/positioner/Popper.tsx | 72 ++++++++++----------
1 file changed, 35 insertions(+), 37 deletions(-)
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
index 745c250ffb..1e06abaf23 100644
--- a/packages/react/src/lib/positioner/Popper.tsx
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -24,50 +24,48 @@ const Popper: React.FunctionComponent = props => {
// positioningDependencies,
} = props
- const [placement, setPlacement] = React.useState(
- getPlacement({ align, position, rtl }),
- )
+ const userPlacement = getPlacement({ align, position, rtl })
+ const [placement, setPlacement] = React.useState(userPlacement)
const popperRef = React.useRef()
const contentRef = React.useRef(null)
- const handleUpdate = React.useCallback((data: PopperJS.Data) => {
- if (data.placement !== placement) {
- console.log('handleUpdate old placement: ', placement)
- console.log('handleUpdate new placement: ', data.placement)
- setPlacement(data.placement)
- }
- }, [])
-
- React.useEffect(() => {
- const options: PopperJS.PopperOptions = {
- placement,
- eventsEnabled,
- positionFixed,
- modifiers: {
- ...(offset && {
- offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
- keepTogether: { enabled: false },
- }),
- ...modifiers,
- arrow: {
- enabled: !!arrowRef.current,
- element: arrowRef.current,
- },
- },
- onCreate: handleUpdate,
- onUpdate: handleUpdate,
- }
-
- popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
-
- return () => popperRef.current.destroy()
- })
+ const handleUpdate = React.useCallback(
+ (data: PopperJS.Data) => {
+ if (data.placement !== placement) {
+ console.log('handleUpdate old placement: ', placement)
+ console.log('handleUpdate new placement: ', data.placement)
+ setPlacement(data.placement)
+ }
+ },
+ [placement],
+ )
React.useEffect(
() => {
- popperRef.current.scheduleUpdate()
+ const options: PopperJS.PopperOptions = {
+ placement: userPlacement,
+ eventsEnabled,
+ positionFixed,
+ modifiers: {
+ ...(offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }),
+ ...modifiers,
+ arrow: {
+ enabled: !!arrowRef.current,
+ element: arrowRef.current,
+ },
+ },
+ onCreate: handleUpdate,
+ onUpdate: handleUpdate,
+ }
+
+ popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
+
+ return () => popperRef.current.destroy()
},
- [placement],
+ [eventsEnabled, handleUpdate, modifiers, positionFixed, userPlacement],
)
return (
From 28c35faccb1d7d422ebccf344a2f18c61661d5bc Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 16 May 2019 00:19:23 +0200
Subject: [PATCH 13/30] fix Popper custom implementation and integrate in Popup
and Dropdown
---
.../src/components/Dropdown/Dropdown.tsx | 35 ++--
packages/react/src/components/Popup/Popup.tsx | 16 +-
.../src/components/Popup/PopupContent.tsx | 4 +-
packages/react/src/lib/positioner/Popper.tsx | 162 +++++++++---------
.../react/src/lib/positioner/Positioner.tsx | 93 ----------
.../positioner/createPopperReferenceProxy.ts | 35 ----
packages/react/src/lib/positioner/index.ts | 3 +-
.../components/Popup/popupContentStyles.ts | 2 +-
8 files changed, 109 insertions(+), 241 deletions(-)
delete mode 100644 packages/react/src/lib/positioner/Positioner.tsx
delete mode 100644 packages/react/src/lib/positioner/createPopperReferenceProxy.ts
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index b888a3cdcb..04f2a0b33d 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -41,7 +41,7 @@ import { screenReaderContainerStyles } from '../../lib/accessibility/Styles/acce
import ListItem from '../List/ListItem'
import Icon, { IconProps } from '../Icon/Icon'
import Portal from '../Portal/Portal'
-import { ALIGNMENTS, POSITIONS, Positioner, PositionCommonProps } from '../../lib/positioner'
+import { ALIGNMENTS, POSITIONS, Popper, PositionCommonProps } from '../../lib/positioner'
export interface DropdownSlotClassNames {
clearIndicator: string
@@ -640,7 +640,6 @@ class Dropdown extends AutoControlledComponent, Dropdo
}
}
- console.log('[Dropdown.renderItemsList')
return (
[ {
@@ -648,30 +647,26 @@ class Dropdown extends AutoControlledComponent, Dropdo
handleRef(innerRef, listElement)
}}
>
- {
- console.log('[Dropdown.Positioner.children')
- return (
- ]
- )
- }}
+ children={() => (
+
+ )}
/>
)
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index 9bf66aa3ec..81b7668ff2 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -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 { PopperChildrenProps } from 'react-popper'
import {
applyAccessibilityKeyHandlers,
@@ -24,7 +23,13 @@ import {
setWhatInputSource,
} from '../../lib'
import { ComponentEventHandler, ShorthandValue } from '../../types'
-import { ALIGNMENTS, POSITIONS, Popper, PositionCommonProps } from '../../lib/positioner'
+import {
+ ALIGNMENTS,
+ POSITIONS,
+ Popper,
+ PositionCommonProps,
+ PopperChildrenProps,
+} from '../../lib/positioner'
import PopupContent from './PopupContent'
import { popupBehavior } from '../../lib/accessibility'
import {
@@ -173,7 +178,7 @@ export default class Popup extends AutoControlledComponent()
+ pointerRef = React.createRef()
triggerRef = React.createRef() as React.MutableRefObject
// focusable element which has triggered Popup, can be either triggerDomElement or the element inside it
triggerFocusableDomElement = null
@@ -389,7 +394,7 @@ export default class Popup extends AutoControlledComponent {
- console.log('[Popup.renderPopperChildren]')
const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
const content = renderContent ? renderContent(scheduleUpdate) : propsContent
const documentRef = toRefObject(mountDocument)
@@ -442,7 +446,7 @@ export default class Popup extends AutoControlledComponent
}
class PopupContent extends UIComponent, any> {
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
index 1e06abaf23..dd69d0503b 100644
--- a/packages/react/src/lib/positioner/Popper.tsx
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -5,75 +5,119 @@ import { Ref } from '@stardust-ui/react-component-ref'
import { getPlacement, applyRtlToOffset } from './positioningHelper'
import { Alignment, Position } from './index'
+export interface PositionCommonProps {
+ /** Alignment for the component. */
+ align?: Alignment
+
+ /** Offset value to apply to rendered component. 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 component element
+ * - vw, CSS viewport width unit
+ * - vh, CSS viewport height unit
+ */
+ offset?: string
+
+ /**
+ * Position for the component. 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
+}
+
+export interface PopperChildrenProps {
+ placement: PopperJS.Placement
+ scheduleUpdate: () => void
+}
+
+interface PopperProps extends PositionCommonProps {
+ pointerRef?: React.RefObject
+ children: (props: PopperChildrenProps) => React.ReactNode
+ eventsEnabled?: boolean
+ modifiers?: PopperJS.Modifiers
+ positionFixed?: boolean
+ targetRef?: React.RefObject
+
+ /**
+ * rtl attribute for the component
+ */
+ rtl?: boolean
+
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
+ */
+ positioningDependencies?: any[]
+}
+
/**
- * Poppers rely on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning.
+ * Popper relies on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning.
*/
const Popper: React.FunctionComponent = props => {
const {
align,
- arrowRef,
+ pointerRef,
children,
eventsEnabled,
offset,
- // placement: userPlacement,
position,
positionFixed,
modifiers,
rtl,
targetRef,
- // positioningDependencies,
+ positioningDependencies,
} = props
- const userPlacement = getPlacement({ align, position, rtl })
- const [placement, setPlacement] = React.useState(userPlacement)
const popperRef = React.useRef()
const contentRef = React.useRef(null)
+ const userPlacement = React.useMemo(() => getPlacement({ align, position, rtl }), [
+ align,
+ position,
+ rtl,
+ ])
+
+ const [placement, setPlacement] = React.useState(userPlacement)
+
const handleUpdate = React.useCallback(
(data: PopperJS.Data) => {
- if (data.placement !== placement) {
- console.log('handleUpdate old placement: ', placement)
- console.log('handleUpdate new placement: ', data.placement)
- setPlacement(data.placement)
- }
+ if (data.placement !== placement) setPlacement(data.placement)
},
[placement],
)
- React.useEffect(
- () => {
- const options: PopperJS.PopperOptions = {
- placement: userPlacement,
- eventsEnabled,
- positionFixed,
- modifiers: {
- ...(offset && {
- offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
- keepTogether: { enabled: false },
- }),
- ...modifiers,
- arrow: {
- enabled: !!arrowRef.current,
- element: arrowRef.current,
- },
+ React.useEffect(() => {
+ const pointerRefElement = pointerRef && pointerRef.current
+ const options: PopperJS.PopperOptions = {
+ placement: userPlacement,
+ eventsEnabled,
+ positionFixed,
+ modifiers: {
+ ...(offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }),
+ ...modifiers,
+ arrow: {
+ enabled: !!pointerRefElement,
+ element: pointerRefElement,
},
- onCreate: handleUpdate,
- onUpdate: handleUpdate,
- }
+ },
+ onCreate: handleUpdate,
+ onUpdate: handleUpdate,
+ }
- popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
+ popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
- return () => popperRef.current.destroy()
- },
- [eventsEnabled, handleUpdate, modifiers, positionFixed, userPlacement],
- )
+ return () => popperRef.current.destroy()
+ }, [eventsEnabled, handleUpdate, modifiers, offset, position, positionFixed, rtl, userPlacement].concat(positioningDependencies))
return (
[
{
children({
placement,
- scheduleUpdate: () => {},
+ scheduleUpdate: popperRef.current && popperRef.current.scheduleUpdate,
}) as React.ReactElement
}
]
@@ -82,53 +126,7 @@ const Popper: React.FunctionComponent = props => {
Popper.defaultProps = {
eventsEnabled: true,
- targetRef: undefined,
positionFixed: false,
}
export default Popper
-
-interface PopperChildrenProps {
- placement: PopperJS.Placement
- scheduleUpdate: () => void
-}
-
-export interface PositionCommonProps {
- /** Alignment for the component. */
- align?: Alignment
-
- /** Offset value to apply to rendered component. 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 component element
- * - vw, CSS viewport width unit
- * - vh, CSS viewport height unit
- */
- offset?: string
-
- /**
- * Position for the component. 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
-}
-
-interface PopperProps extends PositionCommonProps {
- arrowRef?: React.RefObject
- children: (props: PopperChildrenProps) => React.ReactNode
- eventsEnabled?: boolean
- modifiers?: PopperJS.Modifiers
- positionFixed?: boolean
- targetRef?: React.RefObject
-
- /**
- * rtl attribute for the component
- */
- rtl?: boolean
-
- /**
- * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
- */
- positioningDependencies?: any[]
-}
diff --git a/packages/react/src/lib/positioner/Positioner.tsx b/packages/react/src/lib/positioner/Positioner.tsx
deleted file mode 100644
index b0e107d58e..0000000000
--- a/packages/react/src/lib/positioner/Positioner.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import * as React from 'react'
-import { Popper, PopperChildrenProps, PopperProps } from 'react-popper'
-import { Ref } from '@stardust-ui/react-component-ref'
-import { Modifiers } from 'popper.js'
-
-import { Alignment, Position } from './index'
-import { getPlacement, applyRtlToOffset } from './positioningHelper'
-import createPopperReferenceProxy from './createPopperReferenceProxy'
-
-export interface PositionCommonProps {
- /** Alignment for the component. */
- align?: Alignment
-
- /** Offset value to apply to rendered component. 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 component element
- * - vw, CSS viewport width unit
- * - vh, CSS viewport height unit
- */
- offset?: string
-
- /**
- * Position for the component. 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
-}
-
-interface PositionerProps extends PopperProps, PositionCommonProps {
- /**
- * rtl attribute for the component
- */
- rtl?: boolean
-
- /**
- * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
- */
- positioningDependencies?: any[]
-
- /**
- * DOM element or ref representing the target used by the positioning mechanism.
- */
- target?: HTMLElement | React.RefObject
-}
-
-const Positioner: React.FunctionComponent = props => {
- const {
- align,
- children,
- modifiers,
- offset,
- position,
- positioningDependencies,
- rtl,
- target,
- ...rest
- } = props
-
- // https://popper.js.org/popper-documentation.html#modifiers..offset
- const popperModifiers: Modifiers = {
- ...(offset && {
- offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
- keepTogether: { enabled: false },
- }),
- ...modifiers,
- }
-
- const scheduleUpdate = React.useRef(null)
- // const currentStyle = React.useRef(null)
-
- React.useEffect(() => {
- if (scheduleUpdate.current) scheduleUpdate.current()
- }, positioningDependencies)
-
- console.log('[Positioner.render]')
- return (
- {
- console.log('[Positioner.children]')
- scheduleUpdate.current = props.scheduleUpdate
- return [{children(props) as React.ReactElement}]
- }}
- {...rest}
- />
- )
-}
-
-export default Positioner
diff --git a/packages/react/src/lib/positioner/createPopperReferenceProxy.ts b/packages/react/src/lib/positioner/createPopperReferenceProxy.ts
deleted file mode 100644
index 334d129673..0000000000
--- a/packages/react/src/lib/positioner/createPopperReferenceProxy.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { isRefObject, toRefObject } from '@stardust-ui/react-component-ref'
-import * as _ from 'lodash'
-import * as React from 'react'
-import * as PopperJS from 'popper.js'
-
-class ReferenceProxy implements PopperJS.ReferenceObject {
- constructor(private ref: React.RefObject) {}
-
- getBoundingClientRect() {
- return _.invoke(this.ref.current, 'getBoundingClientRect', {})
- }
-
- get clientWidth() {
- return this.getBoundingClientRect().width
- }
-
- get clientHeight() {
- return this.getBoundingClientRect().height
- }
-}
-
-/**
- * Popper.js does not support ref objects from `createRef()` as referenceElement. If we will pass
- * directly `ref`, `ref.current` will be `null` at the render process.
- *
- * @see https://popper.js.org/popper-documentation.html#referenceObject
- * @see https://github.com/FezVrasta/react-popper/blob/v1.3.3/src/Popper.js#L166
- */
-const createPopperReferenceProxy = (reference: HTMLElement | React.RefObject) => {
- const referenceRef = isRefObject(reference) ? reference : toRefObject(reference)
-
- return new ReferenceProxy(referenceRef)
-}
-
-export default createPopperReferenceProxy
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
index b8760bc8c9..a2545a4e75 100644
--- a/packages/react/src/lib/positioner/index.ts
+++ b/packages/react/src/lib/positioner/index.ts
@@ -3,5 +3,4 @@ export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
-export { default as Positioner, PositionCommonProps } from './Positioner'
-export { default as Popper } from './Popper'
+export { default as Popper, PositionCommonProps, PopperChildrenProps } from './Popper'
diff --git a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
index aee6ef5ea1..7bfa7bde52 100644
--- a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
+++ b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
@@ -1,4 +1,4 @@
-import { PopperChildrenProps } from 'react-popper'
+import { PopperChildrenProps } from '../../../../lib/positioner'
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { PopupContentProps } from '../../../../components/Popup/PopupContent'
import { PopupContentVariables } from './popupContentVariables'
From 6732b016008033bfec4eb3c5fd29ed97ca20e212 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 16 May 2019 12:58:54 +0200
Subject: [PATCH 14/30] feat(popper): custom react wrapper
---
CHANGELOG.md | 1 +
packages/react/src/components/Popup/Popup.tsx | 78 +++--------
.../src/components/Popup/PopupContent.tsx | 23 +--
.../Popup/createPopperReferenceProxy.ts | 39 ------
packages/react/src/index.ts | 2 +-
packages/react/src/lib/positioner/Popper.tsx | 132 ++++++++++++++++++
packages/react/src/lib/positioner/index.ts | 6 +
.../positioner}/positioningHelper.ts | 6 +-
.../components/Popup/popupContentStyles.ts | 2 +-
.../specs/components/Popup/Popup-test.tsx | 102 --------------
.../lib/positioner/positioningHelper-test.ts | 122 ++++++++++++++++
11 files changed, 291 insertions(+), 222 deletions(-)
delete mode 100644 packages/react/src/components/Popup/createPopperReferenceProxy.ts
create mode 100644 packages/react/src/lib/positioner/Popper.tsx
create mode 100644 packages/react/src/lib/positioner/index.ts
rename packages/react/src/{components/Popup => lib/positioner}/positioningHelper.ts (93%)
create mode 100644 packages/react/test/specs/lib/positioner/positioningHelper-test.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f800aa84e..09fd62181a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add styles for the dark and high contrast Teams themes for the `Dropdown` component @mnajdova ([#1299](https://github.com/stardust-ui/react/pull/1299))
- Highlight options by character keys in `Dropdown` non-search versions @silviuavram ([#1270](https://github.com/stardust-ui/react/pull/1270))
- Aligned link styles for `Chat.Message` component with latest Teams theme design @Bugaa92 ([#1269](https://github.com/stardust-ui/react/pull/1269))
+- Replace `react-popper` package with custom `Popper` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
## [v0.30.0](https://github.com/stardust-ui/react/tree/v0.30.0) (2019-05-10)
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index c0e265a3fb..81b7668ff2 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -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,
@@ -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,
+ PositionCommonProps,
+ PopperChildrenProps,
+} from '../../lib/positioner'
import PopupContent from './PopupContent'
-
import { popupBehavior } from '../../lib/accessibility'
import {
AutoFocusZone,
@@ -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'
@@ -59,7 +57,8 @@ export interface PopupSlotClassNames {
export interface PopupProps
extends StyledComponentProps,
ChildrenComponentProps,
- ContentComponentProps {
+ ContentComponentProps,
+ PositionCommonProps {
/**
* Accessibility behavior if overridden by the user.
* @default popupBehavior
@@ -67,9 +66,6 @@ export interface PopupProps
* */
accessibility?: Accessibility
- /** Alignment for the popup. */
- align?: Alignment
-
/** Additional CSS class name(s) to apply. */
className?: string
@@ -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
@@ -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.
@@ -140,7 +120,6 @@ export interface PopupProps
export interface PopupState {
open: boolean
- target: HTMLElement
}
/**
@@ -171,6 +150,7 @@ export default class Popup extends AutoControlledComponent()
triggerRef = React.createRef() as React.MutableRefObject
// focusable element which has triggered Popup, can be either triggerDomElement or the element inside it
triggerFocusableDomElement = null
@@ -409,27 +390,17 @@ export default class Popup extends AutoControlledComponent
)
}
@@ -439,13 +410,7 @@ export default class Popup extends AutoControlledComponent {
const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
const content = renderContent ? renderContent(scheduleUpdate) : propsContent
@@ -456,7 +421,6 @@ export default class Popup extends AutoControlledComponent
[ {
- ref(domElement)
this.popupDomElement = domElement
handleRef(contentRef, domElement)
handleRef(nestingRef, domElement)
diff --git a/packages/react/src/components/Popup/PopupContent.tsx b/packages/react/src/components/Popup/PopupContent.tsx
index c70d1ef022..67bbd68be2 100644
--- a/packages/react/src/components/Popup/PopupContent.tsx
+++ b/packages/react/src/components/Popup/PopupContent.tsx
@@ -1,8 +1,8 @@
import { Ref } from '@stardust-ui/react-component-ref'
import * as React from 'react'
-import { PopperChildrenProps } from 'react-popper'
import * as PropTypes from 'prop-types'
import * as _ from 'lodash'
+import * as customPropTypes from '@stardust-ui/react-proptypes'
import {
childrenExist,
@@ -17,6 +17,7 @@ import {
} from '../../lib'
import { Accessibility } from '../../lib/accessibility/types'
import { defaultBehavior } from '../../lib/accessibility'
+import { PopperChildrenProps } from '../../lib/positioner'
import { WithAsProp, ComponentEventHandler, withSafeTypeForAs } from '../../types'
import Box from '../Box/Box'
@@ -51,10 +52,7 @@ export interface PopupContentProps
pointing?: boolean
/** A ref to a pointer element. */
- pointerRef?: PopperChildrenProps['arrowProps']['ref']
-
- /** An object with positioning styles fof a pointer. */
- pointerStyle?: PopperChildrenProps['arrowProps']['style']
+ pointerRef?: React.RefObject
}
class PopupContent extends UIComponent, any> {
@@ -69,8 +67,7 @@ class PopupContent extends UIComponent, any> {
pointing: PropTypes.bool,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
- pointerRef: PropTypes.func,
- pointerStyle: PropTypes.object,
+ pointerRef: customPropTypes.ref,
}
static defaultProps = {
@@ -92,7 +89,7 @@ class PopupContent extends UIComponent, any> {
unhandledProps,
styles,
}: RenderResultConfig): React.ReactNode {
- const { children, content, pointing, pointerRef, pointerStyle } = this.props
+ const { children, content, pointing, pointerRef } = this.props
return (
, any> {
>
{pointing && (
][
- {Box.create(
- {},
- {
- defaultProps: {
- style: pointerStyle,
- styles: styles.pointer,
- },
- },
- )}
+ {Box.create({}, { defaultProps: { styles: styles.pointer } })}
]
)}
diff --git a/packages/react/src/components/Popup/createPopperReferenceProxy.ts b/packages/react/src/components/Popup/createPopperReferenceProxy.ts
deleted file mode 100644
index 59bd7249c7..0000000000
--- a/packages/react/src/components/Popup/createPopperReferenceProxy.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { isRefObject, toRefObject } from '@stardust-ui/react-component-ref'
-import * as _ from 'lodash'
-import * as React from 'react'
-import * as PopperJS from 'popper.js'
-
-class ReferenceProxy implements PopperJS.ReferenceObject {
- ref: React.RefObject
-
- constructor(refObject) {
- this.ref = refObject
- }
-
- getBoundingClientRect() {
- return _.invoke(this.ref.current, 'getBoundingClientRect', {})
- }
-
- get clientWidth() {
- return this.getBoundingClientRect().width
- }
-
- get clientHeight() {
- return this.getBoundingClientRect().height
- }
-}
-
-/**
- * Popper.js does not support ref objects from `createRef()` as referenceElement. If we will pass
- * directly `ref`, `ref.current` will be `null` at the render process.
- *
- * @see https://popper.js.org/popper-documentation.html#referenceObject
- * @see https://github.com/FezVrasta/react-popper/blob/v1.3.3/src/Popper.js#L166
- */
-const createPopperReferenceProxy = (reference: HTMLElement | React.RefObject) => {
- const referenceRef = isRefObject(reference) ? reference : toRefObject(reference)
-
- return new ReferenceProxy(referenceRef)
-}
-
-export default createPopperReferenceProxy
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 666a82bc0f..e4fdc4e73a 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -113,7 +113,7 @@ export {
PopupEventsArray,
} from './components/Popup/Popup'
export { default as PopupContent, PopupContentProps } from './components/Popup/PopupContent'
-export { Placement, Alignment, Position } from './components/Popup/positioningHelper'
+export { Alignment, Position } from './lib/positioner'
export {
default as Portal,
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
new file mode 100644
index 0000000000..dd69d0503b
--- /dev/null
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -0,0 +1,132 @@
+import * as React from 'react'
+import PopperJS from 'popper.js'
+import { Ref } from '@stardust-ui/react-component-ref'
+
+import { getPlacement, applyRtlToOffset } from './positioningHelper'
+import { Alignment, Position } from './index'
+
+export interface PositionCommonProps {
+ /** Alignment for the component. */
+ align?: Alignment
+
+ /** Offset value to apply to rendered component. 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 component element
+ * - vw, CSS viewport width unit
+ * - vh, CSS viewport height unit
+ */
+ offset?: string
+
+ /**
+ * Position for the component. 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
+}
+
+export interface PopperChildrenProps {
+ placement: PopperJS.Placement
+ scheduleUpdate: () => void
+}
+
+interface PopperProps extends PositionCommonProps {
+ pointerRef?: React.RefObject
+ children: (props: PopperChildrenProps) => React.ReactNode
+ eventsEnabled?: boolean
+ modifiers?: PopperJS.Modifiers
+ positionFixed?: boolean
+ targetRef?: React.RefObject
+
+ /**
+ * rtl attribute for the component
+ */
+ rtl?: boolean
+
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
+ */
+ positioningDependencies?: any[]
+}
+
+/**
+ * Popper relies on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning.
+ */
+const Popper: React.FunctionComponent = props => {
+ const {
+ align,
+ pointerRef,
+ children,
+ eventsEnabled,
+ offset,
+ position,
+ positionFixed,
+ modifiers,
+ rtl,
+ targetRef,
+ positioningDependencies,
+ } = props
+
+ const popperRef = React.useRef()
+ const contentRef = React.useRef(null)
+
+ const userPlacement = React.useMemo(() => getPlacement({ align, position, rtl }), [
+ align,
+ position,
+ rtl,
+ ])
+
+ const [placement, setPlacement] = React.useState(userPlacement)
+
+ const handleUpdate = React.useCallback(
+ (data: PopperJS.Data) => {
+ if (data.placement !== placement) setPlacement(data.placement)
+ },
+ [placement],
+ )
+
+ React.useEffect(() => {
+ const pointerRefElement = pointerRef && pointerRef.current
+ const options: PopperJS.PopperOptions = {
+ placement: userPlacement,
+ eventsEnabled,
+ positionFixed,
+ modifiers: {
+ ...(offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }),
+ ...modifiers,
+ arrow: {
+ enabled: !!pointerRefElement,
+ element: pointerRefElement,
+ },
+ },
+ onCreate: handleUpdate,
+ onUpdate: handleUpdate,
+ }
+
+ popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
+
+ return () => popperRef.current.destroy()
+ }, [eventsEnabled, handleUpdate, modifiers, offset, position, positionFixed, rtl, userPlacement].concat(positioningDependencies))
+
+ return (
+ [
+ {
+ children({
+ placement,
+ scheduleUpdate: popperRef.current && popperRef.current.scheduleUpdate,
+ }) as React.ReactElement
+ }
+ ]
+ )
+}
+
+Popper.defaultProps = {
+ eventsEnabled: true,
+ positionFixed: false,
+}
+
+export default Popper
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
new file mode 100644
index 0000000000..a2545a4e75
--- /dev/null
+++ b/packages/react/src/lib/positioner/index.ts
@@ -0,0 +1,6 @@
+export type Position = 'above' | 'below' | 'before' | 'after'
+export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
+export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
+
+export { default as Popper, PositionCommonProps, PopperChildrenProps } from './Popper'
diff --git a/packages/react/src/components/Popup/positioningHelper.ts b/packages/react/src/lib/positioner/positioningHelper.ts
similarity index 93%
rename from packages/react/src/components/Popup/positioningHelper.ts
rename to packages/react/src/lib/positioner/positioningHelper.ts
index 92e74d367a..8a32f642e6 100644
--- a/packages/react/src/components/Popup/positioningHelper.ts
+++ b/packages/react/src/lib/positioner/positioningHelper.ts
@@ -1,8 +1,6 @@
-export { Placement } from 'popper.js'
import { Placement } from 'popper.js'
-export type Position = 'above' | 'below' | 'before' | 'after'
-export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+import { Alignment, Position } from './index'
enum PlacementParts {
top = 'top',
@@ -56,7 +54,7 @@ const shouldAlignToCenter = (p: Position, a: Alignment) => {
* | after | center | right | left
* | after | bottom | right-end | left-end
*/
-export const getPopupPlacement = ({
+export const getPlacement = ({
align,
position,
rtl,
diff --git a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
index aee6ef5ea1..7bfa7bde52 100644
--- a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
+++ b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
@@ -1,4 +1,4 @@
-import { PopperChildrenProps } from 'react-popper'
+import { PopperChildrenProps } from '../../../../lib/positioner'
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { PopupContentProps } from '../../../../components/Popup/PopupContent'
import { PopupContentVariables } from './popupContentVariables'
diff --git a/packages/react/test/specs/components/Popup/Popup-test.tsx b/packages/react/test/specs/components/Popup/Popup-test.tsx
index e392d2f93a..8c2239f712 100644
--- a/packages/react/test/specs/components/Popup/Popup-test.tsx
+++ b/packages/react/test/specs/components/Popup/Popup-test.tsx
@@ -1,12 +1,5 @@
-import { Placement } from 'popper.js'
import * as React from 'react'
-import {
- getPopupPlacement,
- applyRtlToOffset,
- Position,
- Alignment,
-} from 'src/components/Popup/positioningHelper'
import Popup, { PopupEvents } from 'src/components/Popup/Popup'
import { Accessibility } from 'src/lib/accessibility/types'
import { popupFocusTrapBehavior, popupBehavior, dialogBehavior } from 'src/lib/accessibility/index'
@@ -15,33 +8,9 @@ import { domEvent, mountWithProvider } from '../../../utils'
import * as keyboardKey from 'keyboard-key'
import { ReactWrapper } from 'enzyme'
-type PositionTestInput = {
- align: Alignment
- position: Position
- expectedPlacement: Placement
- rtl?: boolean
-}
-
describe('Popup', () => {
const triggerId = 'triggerElement'
const contentId = 'contentId'
- const testPopupPosition = ({
- align,
- position,
- expectedPlacement,
- rtl = false,
- }: PositionTestInput) =>
- it(`Popup ${position} position is transformed to ${expectedPlacement} Popper's placement`, () => {
- const actualPlacement = getPopupPlacement({ align, position, rtl })
- expect(actualPlacement).toEqual(expectedPlacement)
- })
-
- const testPopupPositionInRtl = ({
- align,
- position,
- expectedPlacement,
- }: PositionTestInput & { rtl?: never }) =>
- testPopupPosition({ align, position, expectedPlacement, rtl: true })
const getPopupContent = (popup: ReactWrapper) => {
return popup.find(`div#${contentId}`)
@@ -76,77 +45,6 @@ describe('Popup', () => {
expect(getPopupContent(popup).exists()).toBe(false)
}
- describe('handles Popup position correctly in ltr', () => {
- testPopupPosition({ position: 'above', align: 'start', expectedPlacement: 'top-start' })
- testPopupPosition({ position: 'above', align: 'center', expectedPlacement: 'top' })
- testPopupPosition({ position: 'above', align: 'end', expectedPlacement: 'top-end' })
- testPopupPosition({ position: 'below', align: 'start', expectedPlacement: 'bottom-start' })
- testPopupPosition({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
- testPopupPosition({ position: 'below', align: 'end', expectedPlacement: 'bottom-end' })
- testPopupPosition({ position: 'before', align: 'top', expectedPlacement: 'left-start' })
- testPopupPosition({ position: 'before', align: 'center', expectedPlacement: 'left' })
- testPopupPosition({ position: 'before', align: 'bottom', expectedPlacement: 'left-end' })
- testPopupPosition({ position: 'after', align: 'top', expectedPlacement: 'right-start' })
- testPopupPosition({ position: 'after', align: 'center', expectedPlacement: 'right' })
- testPopupPosition({ position: 'after', align: 'bottom', expectedPlacement: 'right-end' })
- })
-
- describe('handles Popup position correctly in rtl', () => {
- testPopupPositionInRtl({ position: 'above', align: 'start', expectedPlacement: 'top-end' })
- testPopupPositionInRtl({ position: 'above', align: 'center', expectedPlacement: 'top' })
- testPopupPositionInRtl({ position: 'above', align: 'end', expectedPlacement: 'top-start' })
- testPopupPositionInRtl({ position: 'below', align: 'start', expectedPlacement: 'bottom-end' })
- testPopupPositionInRtl({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
- testPopupPositionInRtl({ position: 'below', align: 'end', expectedPlacement: 'bottom-start' })
- testPopupPositionInRtl({ position: 'before', align: 'top', expectedPlacement: 'right-start' })
- testPopupPositionInRtl({ position: 'before', align: 'center', expectedPlacement: 'right' })
- testPopupPositionInRtl({ position: 'before', align: 'bottom', expectedPlacement: 'right-end' })
- testPopupPositionInRtl({ position: 'after', align: 'top', expectedPlacement: 'left-start' })
- testPopupPositionInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
- testPopupPositionInRtl({ position: 'after', align: 'bottom', expectedPlacement: 'left-end' })
- })
-
- describe('Popup offset transformed correctly in RTL', () => {
- it("applies transform only for 'above' and 'below' postioning", () => {
- const originalOffsetValue = '100%'
-
- expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
- expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
-
- expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
- expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
- })
-
- const expectOffsetTransformResult = (originalOffset, resultOffset) => {
- expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
- }
-
- it('flips sign of simple expressions', () => {
- expectOffsetTransformResult('100%', '-100%')
- expectOffsetTransformResult(' 2000%p ', '-2000%p')
- expectOffsetTransformResult('100 ', '-100')
- expectOffsetTransformResult(' - 200vh', '200vh')
- })
-
- it('flips sign of complex expressions', () => {
- expectOffsetTransformResult('100% + 200', '-100% - 200')
- expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
- })
-
- it('transforms only horizontal offset value', () => {
- const xOffset = '-100%'
- const yOffset = '800vh'
-
- const offsetValue = [xOffset, yOffset].join(',')
- const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
- ',',
- )
-
- expect(xOffsetTransformed.trim()).not.toBe(xOffset)
- expect(yOffsetTransformed.trim()).toBe(yOffset)
- })
- })
-
describe('onOpenChange', () => {
test('is called on click', () => {
const spy = jest.fn()
diff --git a/packages/react/test/specs/lib/positioner/positioningHelper-test.ts b/packages/react/test/specs/lib/positioner/positioningHelper-test.ts
new file mode 100644
index 0000000000..370b6e19dd
--- /dev/null
+++ b/packages/react/test/specs/lib/positioner/positioningHelper-test.ts
@@ -0,0 +1,122 @@
+import { Placement } from 'popper.js'
+
+import { Alignment, Position } from 'src/lib/positioner'
+import { getPlacement, applyRtlToOffset } from 'src/lib/positioner/positioningHelper'
+
+type PositionTestInput = {
+ align: Alignment
+ position: Position
+ expectedPlacement: Placement
+ rtl?: boolean
+}
+
+describe('positioningHelper', () => {
+ const testPositioningHelper = ({
+ align,
+ position,
+ expectedPlacement,
+ rtl = false,
+ }: PositionTestInput) =>
+ it(`positioningHelper ${position} position argument is transformed to ${expectedPlacement} Popper's placement`, () => {
+ const actualPlacement = getPlacement({ align, position, rtl })
+ expect(actualPlacement).toEqual(expectedPlacement)
+ })
+
+ const testPositioningHelperInRtl = ({
+ align,
+ position,
+ expectedPlacement,
+ }: PositionTestInput & { rtl?: never }) =>
+ testPositioningHelper({ align, position, expectedPlacement, rtl: true })
+
+ describe('handles positioningHelper position argument correctly in ltr', () => {
+ testPositioningHelper({ position: 'above', align: 'start', expectedPlacement: 'top-start' })
+ testPositioningHelper({ position: 'above', align: 'center', expectedPlacement: 'top' })
+ testPositioningHelper({ position: 'above', align: 'end', expectedPlacement: 'top-end' })
+ testPositioningHelper({ position: 'below', align: 'start', expectedPlacement: 'bottom-start' })
+ testPositioningHelper({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
+ testPositioningHelper({ position: 'below', align: 'end', expectedPlacement: 'bottom-end' })
+ testPositioningHelper({ position: 'before', align: 'top', expectedPlacement: 'left-start' })
+ testPositioningHelper({ position: 'before', align: 'center', expectedPlacement: 'left' })
+ testPositioningHelper({ position: 'before', align: 'bottom', expectedPlacement: 'left-end' })
+ testPositioningHelper({ position: 'after', align: 'top', expectedPlacement: 'right-start' })
+ testPositioningHelper({ position: 'after', align: 'center', expectedPlacement: 'right' })
+ testPositioningHelper({ position: 'after', align: 'bottom', expectedPlacement: 'right-end' })
+ })
+
+ describe('handles positioningHelper position argument correctly in rtl', () => {
+ testPositioningHelperInRtl({ position: 'above', align: 'start', expectedPlacement: 'top-end' })
+ testPositioningHelperInRtl({ position: 'above', align: 'center', expectedPlacement: 'top' })
+ testPositioningHelperInRtl({ position: 'above', align: 'end', expectedPlacement: 'top-start' })
+ testPositioningHelperInRtl({
+ position: 'below',
+ align: 'start',
+ expectedPlacement: 'bottom-end',
+ })
+ testPositioningHelperInRtl({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
+ testPositioningHelperInRtl({
+ position: 'below',
+ align: 'end',
+ expectedPlacement: 'bottom-start',
+ })
+ testPositioningHelperInRtl({
+ position: 'before',
+ align: 'top',
+ expectedPlacement: 'right-start',
+ })
+ testPositioningHelperInRtl({ position: 'before', align: 'center', expectedPlacement: 'right' })
+ testPositioningHelperInRtl({
+ position: 'before',
+ align: 'bottom',
+ expectedPlacement: 'right-end',
+ })
+ testPositioningHelperInRtl({ position: 'after', align: 'top', expectedPlacement: 'left-start' })
+ testPositioningHelperInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
+ testPositioningHelperInRtl({
+ position: 'after',
+ align: 'bottom',
+ expectedPlacement: 'left-end',
+ })
+ })
+
+ describe('positioningHelper offset argument transformed correctly in RTL', () => {
+ it("applies transform only for 'above' and 'below' postioning", () => {
+ const originalOffsetValue = '100%'
+
+ expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
+
+ expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
+ })
+
+ const expectOffsetTransformResult = (originalOffset, resultOffset) => {
+ expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
+ }
+
+ it('flips sign of simple expressions', () => {
+ expectOffsetTransformResult('100%', '-100%')
+ expectOffsetTransformResult(' 2000%p ', '-2000%p')
+ expectOffsetTransformResult('100 ', '-100')
+ expectOffsetTransformResult(' - 200vh', '200vh')
+ })
+
+ it('flips sign of complex expressions', () => {
+ expectOffsetTransformResult('100% + 200', '-100% - 200')
+ expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
+ })
+
+ it('transforms only horizontal offset value', () => {
+ const xOffset = '-100%'
+ const yOffset = '800vh'
+
+ const offsetValue = [xOffset, yOffset].join(',')
+ const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
+ ',',
+ )
+
+ expect(xOffsetTransformed.trim()).not.toBe(xOffset)
+ expect(yOffsetTransformed.trim()).toBe(yOffset)
+ })
+ })
+})
From 8bf400c6262b7b1798663cf71d9fcc10f7feeb0b Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 20 May 2019 09:57:55 +0200
Subject: [PATCH 15/30] feat(popper): custom react wrapper
---
.../Usage/PopupExampleAsync.shorthand.tsx | 41 +++--
packages/react/src/components/Popup/Popup.tsx | 91 +++-------
.../src/components/Popup/PopupContent.tsx | 23 +--
.../Popup/createPopperReferenceProxy.ts | 48 -----
packages/react/src/index.ts | 2 +-
packages/react/src/lib/positioner/Popper.tsx | 171 ++++++++++++++++++
packages/react/src/lib/positioner/index.ts | 6 +
.../positioner}/positioningHelper.ts | 6 +-
.../components/Popup/popupContentStyles.ts | 2 +-
.../specs/components/Popup/Popup-test.tsx | 102 -----------
.../lib/positioner/positioningHelper-test.ts | 122 +++++++++++++
11 files changed, 353 insertions(+), 261 deletions(-)
delete mode 100644 packages/react/src/components/Popup/createPopperReferenceProxy.ts
create mode 100644 packages/react/src/lib/positioner/Popper.tsx
create mode 100644 packages/react/src/lib/positioner/index.ts
rename packages/react/src/{components/Popup => lib/positioner}/positioningHelper.ts (93%)
create mode 100644 packages/react/test/specs/lib/positioner/positioningHelper-test.ts
diff --git a/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx b/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx
index 860fcf915b..9df66b2797 100644
--- a/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx
@@ -1,30 +1,31 @@
import * as React from 'react'
-import { Button, Popup, Segment } from '@stardust-ui/react'
+import { Button, Popup, Segment, Loader } from '@stardust-ui/react'
-class AsyncDataLoader extends React.Component {
- state = {
- data: 'loading..',
- }
+const AsyncDataLoader: React.FunctionComponent<{ onLoaded: Function }> = props => {
+ const [data, setData] = React.useState()
- componentDidMount() {
+ React.useEffect(() => {
setTimeout(() => {
- this.setState({
- data: Hello from loaded data!,
- })
- this.props.onLoaded()
+ setData(Hello from loaded data!)
+ props.onLoaded()
}, 1000)
- }
+ }, [])
- render() {
- return this.state.data
- }
+ return data
}
-const PopupExampleAsync = () => (
- }
- renderContent={updatePosition => ({ content: })}
- />
-)
+const PopupExampleAsync = () => {
+ const [loading, setLoading] = React.useState(true)
+ return (
+ {
+ if (!data.open) setLoading(true)
+ }}
+ trigger={}
+ positioningDependencies={[loading]}
+ content={{ content: setLoading(false)} /> }}
+ />
+ )
+}
export default PopupExampleAsync
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index c0e265a3fb..5bdbd756f7 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -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,
@@ -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,
+ PositionCommonProps,
+ PopperChildrenProps,
+} from '../../lib/positioner'
import PopupContent from './PopupContent'
-
import { popupBehavior } from '../../lib/accessibility'
import {
AutoFocusZone,
@@ -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'
@@ -59,7 +57,8 @@ export interface PopupSlotClassNames {
export interface PopupProps
extends StyledComponentProps,
ChildrenComponentProps,
- ContentComponentProps {
+ ContentComponentProps,
+ PositionCommonProps {
/**
* Accessibility behavior if overridden by the user.
* @default popupBehavior
@@ -67,9 +66,6 @@ export interface PopupProps
* */
accessibility?: Accessibility
- /** Alignment for the popup. */
- align?: Alignment
-
/** Additional CSS class name(s) to apply. */
className?: string
@@ -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
@@ -113,19 +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.
- */
- renderContent?: (updatePosition: Function) => ShorthandValue
-
/**
* DOM element that should be used as popup's target - instead of 'trigger' element that is used by default.
*/
@@ -140,7 +114,6 @@ export interface PopupProps
export interface PopupState {
open: boolean
- target: HTMLElement
}
/**
@@ -171,6 +144,7 @@ export default class Popup extends AutoControlledComponent()
triggerRef = React.createRef() as React.MutableRefObject
// focusable element which has triggered Popup, can be either triggerDomElement or the element inside it
triggerFocusableDomElement = null
@@ -409,27 +384,18 @@ export default class Popup extends AutoControlledComponent
)
}
@@ -438,17 +404,9 @@ export default class Popup extends AutoControlledComponent {
- const { content: propsContent, renderContent, contentRef, mountDocument, pointing } = this.props
- const content = renderContent ? renderContent(scheduleUpdate) : propsContent
+ const { content, contentRef, mountDocument, pointing } = this.props
const documentRef = toRefObject(mountDocument)
const popupWrapperAttributes = {
@@ -456,7 +414,6 @@ export default class Popup extends AutoControlledComponent
[ {
- ref(domElement)
this.popupDomElement = domElement
handleRef(contentRef, domElement)
handleRef(nestingRef, domElement)
diff --git a/packages/react/src/components/Popup/PopupContent.tsx b/packages/react/src/components/Popup/PopupContent.tsx
index c70d1ef022..67bbd68be2 100644
--- a/packages/react/src/components/Popup/PopupContent.tsx
+++ b/packages/react/src/components/Popup/PopupContent.tsx
@@ -1,8 +1,8 @@
import { Ref } from '@stardust-ui/react-component-ref'
import * as React from 'react'
-import { PopperChildrenProps } from 'react-popper'
import * as PropTypes from 'prop-types'
import * as _ from 'lodash'
+import * as customPropTypes from '@stardust-ui/react-proptypes'
import {
childrenExist,
@@ -17,6 +17,7 @@ import {
} from '../../lib'
import { Accessibility } from '../../lib/accessibility/types'
import { defaultBehavior } from '../../lib/accessibility'
+import { PopperChildrenProps } from '../../lib/positioner'
import { WithAsProp, ComponentEventHandler, withSafeTypeForAs } from '../../types'
import Box from '../Box/Box'
@@ -51,10 +52,7 @@ export interface PopupContentProps
pointing?: boolean
/** A ref to a pointer element. */
- pointerRef?: PopperChildrenProps['arrowProps']['ref']
-
- /** An object with positioning styles fof a pointer. */
- pointerStyle?: PopperChildrenProps['arrowProps']['style']
+ pointerRef?: React.RefObject
}
class PopupContent extends UIComponent, any> {
@@ -69,8 +67,7 @@ class PopupContent extends UIComponent, any> {
pointing: PropTypes.bool,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
- pointerRef: PropTypes.func,
- pointerStyle: PropTypes.object,
+ pointerRef: customPropTypes.ref,
}
static defaultProps = {
@@ -92,7 +89,7 @@ class PopupContent extends UIComponent, any> {
unhandledProps,
styles,
}: RenderResultConfig): React.ReactNode {
- const { children, content, pointing, pointerRef, pointerStyle } = this.props
+ const { children, content, pointing, pointerRef } = this.props
return (
, any> {
>
{pointing && (
][
- {Box.create(
- {},
- {
- defaultProps: {
- style: pointerStyle,
- styles: styles.pointer,
- },
- },
- )}
+ {Box.create({}, { defaultProps: { styles: styles.pointer } })}
]
)}
diff --git a/packages/react/src/components/Popup/createPopperReferenceProxy.ts b/packages/react/src/components/Popup/createPopperReferenceProxy.ts
deleted file mode 100644
index 48aca30e5a..0000000000
--- a/packages/react/src/components/Popup/createPopperReferenceProxy.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { isRefObject, toRefObject } from '@stardust-ui/react-component-ref'
-import * as _ from 'lodash'
-import * as React from 'react'
-import * as PopperJS from 'popper.js'
-
-class ReferenceProxy implements PopperJS.ReferenceObject {
- ref: React.RefObject
-
- constructor(refObject) {
- this.ref = refObject
- }
-
- getBoundingClientRect() {
- return _.invoke(this.ref.current, 'getBoundingClientRect', {})
- }
-
- get clientWidth() {
- return this.getBoundingClientRect().width
- }
-
- get clientHeight() {
- return this.getBoundingClientRect().height
- }
-
- /**
- * Required to allow properly finding a node to attach scroll.
- *
- * @see https://github.com/FezVrasta/popper.js/blob/v1.15.0/packages/popper/src/utils/getParentNode.js#L12
- */
- get parentNode() {
- return this.ref.current ? this.ref.current.parentNode : undefined
- }
-}
-
-/**
- * Popper.js does not support ref objects from `createRef()` as referenceElement. If we will pass
- * directly `ref`, `ref.current` will be `null` at the render process.
- *
- * @see https://popper.js.org/popper-documentation.html#referenceObject
- * @see https://github.com/FezVrasta/react-popper/blob/v1.3.3/src/Popper.js#L166
- */
-const createPopperReferenceProxy = (reference: HTMLElement | React.RefObject) => {
- const referenceRef = isRefObject(reference) ? reference : toRefObject(reference)
-
- return new ReferenceProxy(referenceRef)
-}
-
-export default createPopperReferenceProxy
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 666a82bc0f..e4fdc4e73a 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -113,7 +113,7 @@ export {
PopupEventsArray,
} from './components/Popup/Popup'
export { default as PopupContent, PopupContentProps } from './components/Popup/PopupContent'
-export { Placement, Alignment, Position } from './components/Popup/positioningHelper'
+export { Alignment, Position } from './lib/positioner'
export {
default as Portal,
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
new file mode 100644
index 0000000000..7e93112ab3
--- /dev/null
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -0,0 +1,171 @@
+import * as React from 'react'
+import PopperJS from 'popper.js'
+import { Ref } from '@stardust-ui/react-component-ref'
+
+import { getPlacement, applyRtlToOffset } from './positioningHelper'
+import { Alignment, Position } from './index'
+
+export interface PositionCommonProps {
+ /** Alignment for the component. */
+ align?: Alignment
+
+ /** Offset value to apply to rendered component. 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 component element
+ * - vw, CSS viewport width unit
+ * - vh, CSS viewport height unit
+ */
+ offset?: string
+
+ /**
+ * Position for the component. 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
+
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
+ */
+ positioningDependencies?: any[]
+}
+
+export interface PopperChildrenProps {
+ /**
+ * Popper's placement.
+ */
+ placement: PopperJS.Placement
+}
+
+type PopperChildrenFn = (props: PopperChildrenProps) => React.ReactNode
+
+interface PopperProps extends PositionCommonProps {
+ /**
+ * Ref object containing the pointer node.
+ */
+ pointerRef?: React.RefObject
+
+ /**
+ * The content of the Popper box (the element that is going to be repositioned).
+ */
+ children: PopperChildrenFn | React.ReactNode
+
+ /**
+ * Enables events (resize, scroll).
+ * @prop {Boolean} eventsEnabled=true
+ */
+ eventsEnabled?: boolean
+
+ /**
+ * List of modifiers used to modify the offsets before they are applied to the Popper box.
+ * They provide most of the functionality of Popper.js.
+ */
+ modifiers?: PopperJS.Modifiers
+
+ /**
+ * Enables the Popper box to position itself in 'fixed' mode (default value is position: 'absolute')
+ * @prop {Boolean} positionFixed=false
+ */
+ positionFixed?: boolean
+
+ /**
+ * Ref object containing the target node (the element that we're using as reference for Popper box).
+ */
+ targetRef?: React.RefObject
+
+ /**
+ * Rtl attribute for the component.
+ */
+ rtl?: boolean
+
+ /**
+ * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
+ */
+ positioningDependencies?: any[]
+}
+
+/**
+ * Popper relies on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning.
+ */
+const Popper: React.FunctionComponent = props => {
+ const {
+ align,
+ children,
+ eventsEnabled,
+ modifiers,
+ offset,
+ pointerRef,
+ position,
+ positionFixed,
+ positioningDependencies,
+ rtl,
+ targetRef,
+ } = props
+
+ const proposedPlacement = getPlacement({ align, position, rtl })
+
+ const popperRef = React.useRef()
+ const contentRef = React.useRef(null)
+ const latestPlacement = React.useRef(proposedPlacement)
+ const [computedPlacement, setComputedPlacement] = React.useState(
+ proposedPlacement,
+ )
+
+ React.useEffect(
+ () => {
+ const handleUpdate = (data: PopperJS.Data) => {
+ if (data.placement !== latestPlacement.current) {
+ latestPlacement.current = data.placement
+ setComputedPlacement(data.placement)
+ }
+ }
+
+ const pointerRefElement = pointerRef && pointerRef.current
+ const options: PopperJS.PopperOptions = {
+ placement: proposedPlacement,
+ eventsEnabled,
+ positionFixed,
+ modifiers: {
+ ...(offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ }),
+ ...modifiers,
+ arrow: {
+ enabled: !!pointerRefElement,
+ element: pointerRefElement,
+ },
+ },
+ onCreate: handleUpdate,
+ onUpdate: handleUpdate,
+ }
+
+ popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
+ return () => popperRef.current.destroy()
+ },
+ [eventsEnabled, modifiers, offset, position, positionFixed, rtl, proposedPlacement],
+ )
+
+ React.useEffect(
+ () => {
+ popperRef.current.scheduleUpdate()
+ },
+ [...(positioningDependencies || []), computedPlacement],
+ )
+
+ let processedChildren = children
+ if (typeof children === 'function') {
+ processedChildren = (children as PopperChildrenFn)({ placement: computedPlacement })
+ }
+
+ return [{processedChildren as React.ReactElement}]
+}
+
+Popper.defaultProps = {
+ eventsEnabled: true,
+ positionFixed: false,
+ positioningDependencies: [],
+}
+
+export default Popper
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
new file mode 100644
index 0000000000..a2545a4e75
--- /dev/null
+++ b/packages/react/src/lib/positioner/index.ts
@@ -0,0 +1,6 @@
+export type Position = 'above' | 'below' | 'before' | 'after'
+export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
+export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
+
+export { default as Popper, PositionCommonProps, PopperChildrenProps } from './Popper'
diff --git a/packages/react/src/components/Popup/positioningHelper.ts b/packages/react/src/lib/positioner/positioningHelper.ts
similarity index 93%
rename from packages/react/src/components/Popup/positioningHelper.ts
rename to packages/react/src/lib/positioner/positioningHelper.ts
index 92e74d367a..8a32f642e6 100644
--- a/packages/react/src/components/Popup/positioningHelper.ts
+++ b/packages/react/src/lib/positioner/positioningHelper.ts
@@ -1,8 +1,6 @@
-export { Placement } from 'popper.js'
import { Placement } from 'popper.js'
-export type Position = 'above' | 'below' | 'before' | 'after'
-export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+import { Alignment, Position } from './index'
enum PlacementParts {
top = 'top',
@@ -56,7 +54,7 @@ const shouldAlignToCenter = (p: Position, a: Alignment) => {
* | after | center | right | left
* | after | bottom | right-end | left-end
*/
-export const getPopupPlacement = ({
+export const getPlacement = ({
align,
position,
rtl,
diff --git a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
index aee6ef5ea1..7bfa7bde52 100644
--- a/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
+++ b/packages/react/src/themes/teams/components/Popup/popupContentStyles.ts
@@ -1,4 +1,4 @@
-import { PopperChildrenProps } from 'react-popper'
+import { PopperChildrenProps } from '../../../../lib/positioner'
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { PopupContentProps } from '../../../../components/Popup/PopupContent'
import { PopupContentVariables } from './popupContentVariables'
diff --git a/packages/react/test/specs/components/Popup/Popup-test.tsx b/packages/react/test/specs/components/Popup/Popup-test.tsx
index e392d2f93a..8c2239f712 100644
--- a/packages/react/test/specs/components/Popup/Popup-test.tsx
+++ b/packages/react/test/specs/components/Popup/Popup-test.tsx
@@ -1,12 +1,5 @@
-import { Placement } from 'popper.js'
import * as React from 'react'
-import {
- getPopupPlacement,
- applyRtlToOffset,
- Position,
- Alignment,
-} from 'src/components/Popup/positioningHelper'
import Popup, { PopupEvents } from 'src/components/Popup/Popup'
import { Accessibility } from 'src/lib/accessibility/types'
import { popupFocusTrapBehavior, popupBehavior, dialogBehavior } from 'src/lib/accessibility/index'
@@ -15,33 +8,9 @@ import { domEvent, mountWithProvider } from '../../../utils'
import * as keyboardKey from 'keyboard-key'
import { ReactWrapper } from 'enzyme'
-type PositionTestInput = {
- align: Alignment
- position: Position
- expectedPlacement: Placement
- rtl?: boolean
-}
-
describe('Popup', () => {
const triggerId = 'triggerElement'
const contentId = 'contentId'
- const testPopupPosition = ({
- align,
- position,
- expectedPlacement,
- rtl = false,
- }: PositionTestInput) =>
- it(`Popup ${position} position is transformed to ${expectedPlacement} Popper's placement`, () => {
- const actualPlacement = getPopupPlacement({ align, position, rtl })
- expect(actualPlacement).toEqual(expectedPlacement)
- })
-
- const testPopupPositionInRtl = ({
- align,
- position,
- expectedPlacement,
- }: PositionTestInput & { rtl?: never }) =>
- testPopupPosition({ align, position, expectedPlacement, rtl: true })
const getPopupContent = (popup: ReactWrapper) => {
return popup.find(`div#${contentId}`)
@@ -76,77 +45,6 @@ describe('Popup', () => {
expect(getPopupContent(popup).exists()).toBe(false)
}
- describe('handles Popup position correctly in ltr', () => {
- testPopupPosition({ position: 'above', align: 'start', expectedPlacement: 'top-start' })
- testPopupPosition({ position: 'above', align: 'center', expectedPlacement: 'top' })
- testPopupPosition({ position: 'above', align: 'end', expectedPlacement: 'top-end' })
- testPopupPosition({ position: 'below', align: 'start', expectedPlacement: 'bottom-start' })
- testPopupPosition({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
- testPopupPosition({ position: 'below', align: 'end', expectedPlacement: 'bottom-end' })
- testPopupPosition({ position: 'before', align: 'top', expectedPlacement: 'left-start' })
- testPopupPosition({ position: 'before', align: 'center', expectedPlacement: 'left' })
- testPopupPosition({ position: 'before', align: 'bottom', expectedPlacement: 'left-end' })
- testPopupPosition({ position: 'after', align: 'top', expectedPlacement: 'right-start' })
- testPopupPosition({ position: 'after', align: 'center', expectedPlacement: 'right' })
- testPopupPosition({ position: 'after', align: 'bottom', expectedPlacement: 'right-end' })
- })
-
- describe('handles Popup position correctly in rtl', () => {
- testPopupPositionInRtl({ position: 'above', align: 'start', expectedPlacement: 'top-end' })
- testPopupPositionInRtl({ position: 'above', align: 'center', expectedPlacement: 'top' })
- testPopupPositionInRtl({ position: 'above', align: 'end', expectedPlacement: 'top-start' })
- testPopupPositionInRtl({ position: 'below', align: 'start', expectedPlacement: 'bottom-end' })
- testPopupPositionInRtl({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
- testPopupPositionInRtl({ position: 'below', align: 'end', expectedPlacement: 'bottom-start' })
- testPopupPositionInRtl({ position: 'before', align: 'top', expectedPlacement: 'right-start' })
- testPopupPositionInRtl({ position: 'before', align: 'center', expectedPlacement: 'right' })
- testPopupPositionInRtl({ position: 'before', align: 'bottom', expectedPlacement: 'right-end' })
- testPopupPositionInRtl({ position: 'after', align: 'top', expectedPlacement: 'left-start' })
- testPopupPositionInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
- testPopupPositionInRtl({ position: 'after', align: 'bottom', expectedPlacement: 'left-end' })
- })
-
- describe('Popup offset transformed correctly in RTL', () => {
- it("applies transform only for 'above' and 'below' postioning", () => {
- const originalOffsetValue = '100%'
-
- expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
- expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
-
- expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
- expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
- })
-
- const expectOffsetTransformResult = (originalOffset, resultOffset) => {
- expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
- }
-
- it('flips sign of simple expressions', () => {
- expectOffsetTransformResult('100%', '-100%')
- expectOffsetTransformResult(' 2000%p ', '-2000%p')
- expectOffsetTransformResult('100 ', '-100')
- expectOffsetTransformResult(' - 200vh', '200vh')
- })
-
- it('flips sign of complex expressions', () => {
- expectOffsetTransformResult('100% + 200', '-100% - 200')
- expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
- })
-
- it('transforms only horizontal offset value', () => {
- const xOffset = '-100%'
- const yOffset = '800vh'
-
- const offsetValue = [xOffset, yOffset].join(',')
- const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
- ',',
- )
-
- expect(xOffsetTransformed.trim()).not.toBe(xOffset)
- expect(yOffsetTransformed.trim()).toBe(yOffset)
- })
- })
-
describe('onOpenChange', () => {
test('is called on click', () => {
const spy = jest.fn()
diff --git a/packages/react/test/specs/lib/positioner/positioningHelper-test.ts b/packages/react/test/specs/lib/positioner/positioningHelper-test.ts
new file mode 100644
index 0000000000..370b6e19dd
--- /dev/null
+++ b/packages/react/test/specs/lib/positioner/positioningHelper-test.ts
@@ -0,0 +1,122 @@
+import { Placement } from 'popper.js'
+
+import { Alignment, Position } from 'src/lib/positioner'
+import { getPlacement, applyRtlToOffset } from 'src/lib/positioner/positioningHelper'
+
+type PositionTestInput = {
+ align: Alignment
+ position: Position
+ expectedPlacement: Placement
+ rtl?: boolean
+}
+
+describe('positioningHelper', () => {
+ const testPositioningHelper = ({
+ align,
+ position,
+ expectedPlacement,
+ rtl = false,
+ }: PositionTestInput) =>
+ it(`positioningHelper ${position} position argument is transformed to ${expectedPlacement} Popper's placement`, () => {
+ const actualPlacement = getPlacement({ align, position, rtl })
+ expect(actualPlacement).toEqual(expectedPlacement)
+ })
+
+ const testPositioningHelperInRtl = ({
+ align,
+ position,
+ expectedPlacement,
+ }: PositionTestInput & { rtl?: never }) =>
+ testPositioningHelper({ align, position, expectedPlacement, rtl: true })
+
+ describe('handles positioningHelper position argument correctly in ltr', () => {
+ testPositioningHelper({ position: 'above', align: 'start', expectedPlacement: 'top-start' })
+ testPositioningHelper({ position: 'above', align: 'center', expectedPlacement: 'top' })
+ testPositioningHelper({ position: 'above', align: 'end', expectedPlacement: 'top-end' })
+ testPositioningHelper({ position: 'below', align: 'start', expectedPlacement: 'bottom-start' })
+ testPositioningHelper({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
+ testPositioningHelper({ position: 'below', align: 'end', expectedPlacement: 'bottom-end' })
+ testPositioningHelper({ position: 'before', align: 'top', expectedPlacement: 'left-start' })
+ testPositioningHelper({ position: 'before', align: 'center', expectedPlacement: 'left' })
+ testPositioningHelper({ position: 'before', align: 'bottom', expectedPlacement: 'left-end' })
+ testPositioningHelper({ position: 'after', align: 'top', expectedPlacement: 'right-start' })
+ testPositioningHelper({ position: 'after', align: 'center', expectedPlacement: 'right' })
+ testPositioningHelper({ position: 'after', align: 'bottom', expectedPlacement: 'right-end' })
+ })
+
+ describe('handles positioningHelper position argument correctly in rtl', () => {
+ testPositioningHelperInRtl({ position: 'above', align: 'start', expectedPlacement: 'top-end' })
+ testPositioningHelperInRtl({ position: 'above', align: 'center', expectedPlacement: 'top' })
+ testPositioningHelperInRtl({ position: 'above', align: 'end', expectedPlacement: 'top-start' })
+ testPositioningHelperInRtl({
+ position: 'below',
+ align: 'start',
+ expectedPlacement: 'bottom-end',
+ })
+ testPositioningHelperInRtl({ position: 'below', align: 'center', expectedPlacement: 'bottom' })
+ testPositioningHelperInRtl({
+ position: 'below',
+ align: 'end',
+ expectedPlacement: 'bottom-start',
+ })
+ testPositioningHelperInRtl({
+ position: 'before',
+ align: 'top',
+ expectedPlacement: 'right-start',
+ })
+ testPositioningHelperInRtl({ position: 'before', align: 'center', expectedPlacement: 'right' })
+ testPositioningHelperInRtl({
+ position: 'before',
+ align: 'bottom',
+ expectedPlacement: 'right-end',
+ })
+ testPositioningHelperInRtl({ position: 'after', align: 'top', expectedPlacement: 'left-start' })
+ testPositioningHelperInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
+ testPositioningHelperInRtl({
+ position: 'after',
+ align: 'bottom',
+ expectedPlacement: 'left-end',
+ })
+ })
+
+ describe('positioningHelper offset argument transformed correctly in RTL', () => {
+ it("applies transform only for 'above' and 'below' postioning", () => {
+ const originalOffsetValue = '100%'
+
+ expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
+
+ expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
+ })
+
+ const expectOffsetTransformResult = (originalOffset, resultOffset) => {
+ expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
+ }
+
+ it('flips sign of simple expressions', () => {
+ expectOffsetTransformResult('100%', '-100%')
+ expectOffsetTransformResult(' 2000%p ', '-2000%p')
+ expectOffsetTransformResult('100 ', '-100')
+ expectOffsetTransformResult(' - 200vh', '200vh')
+ })
+
+ it('flips sign of complex expressions', () => {
+ expectOffsetTransformResult('100% + 200', '-100% - 200')
+ expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
+ })
+
+ it('transforms only horizontal offset value', () => {
+ const xOffset = '-100%'
+ const yOffset = '800vh'
+
+ const offsetValue = [xOffset, yOffset].join(',')
+ const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
+ ',',
+ )
+
+ expect(xOffsetTransformed.trim()).not.toBe(xOffset)
+ expect(yOffsetTransformed.trim()).toBe(yOffset)
+ })
+ })
+})
From d03b12522c62bc9fd599410dc0d4d660ffd89e4f Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 20 May 2019 09:58:43 +0200
Subject: [PATCH 16/30] changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 502a214dde..b65b75d029 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Aligned focus styles for `Chat.Message` component with latest Teams theme design @Bugaa92 ([#1269](https://github.com/stardust-ui/react/pull/1269))
- Fix styles for `fill` prop of `Flex` @kuzhelov ([#1352](https://github.com/stardust-ui/react/pull/1352))
- FontAwesome icons is not part of Teams theme more @layershifter ([#1337](https://github.com/stardust-ui/react/pull/1337))
+- Replace `react-popper` package with custom `Popper` component @Bugaa92 ([#1358](https://github.com/stardust-ui/react/pull/1358))
### Fixes
- Fixed `Flex.Item` children prop type @mnajdova ([#1320](https://github.com/stardust-ui/react/pull/1320))
From 1dd557434d9eff41cc2777db686b8e49a58bdda7 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 20 May 2019 15:51:57 +0200
Subject: [PATCH 17/30] addressed comments
---
.../src/components/Popup/PopupContent.tsx | 4 +-
packages/react/src/lib/positioner/Popper.tsx | 41 ++++++++++---------
packages/react/src/lib/positioner/index.ts | 6 +--
.../src/lib/positioner/positioningHelper.ts | 2 +-
packages/react/src/lib/positioner/types.ts | 4 ++
5 files changed, 30 insertions(+), 27 deletions(-)
create mode 100644 packages/react/src/lib/positioner/types.ts
diff --git a/packages/react/src/components/Popup/PopupContent.tsx b/packages/react/src/components/Popup/PopupContent.tsx
index 67bbd68be2..357fe3165b 100644
--- a/packages/react/src/components/Popup/PopupContent.tsx
+++ b/packages/react/src/components/Popup/PopupContent.tsx
@@ -52,10 +52,10 @@ export interface PopupContentProps
pointing?: boolean
/** A ref to a pointer element. */
- pointerRef?: React.RefObject
+ pointerRef?: React.Ref
}
-class PopupContent extends UIComponent, any> {
+class PopupContent extends UIComponent> {
public static create: Function
public static displayName = 'PopupContent'
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
index 7e93112ab3..ba5bfdf5d3 100644
--- a/packages/react/src/lib/positioner/Popper.tsx
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -3,7 +3,7 @@ import PopperJS from 'popper.js'
import { Ref } from '@stardust-ui/react-component-ref'
import { getPlacement, applyRtlToOffset } from './positioningHelper'
-import { Alignment, Position } from './index'
+import { Alignment, Position } from './types'
export interface PositionCommonProps {
/** Alignment for the component. */
@@ -28,7 +28,7 @@ export interface PositionCommonProps {
/**
* Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
*/
- positioningDependencies?: any[]
+ positioningDependencies?: React.DependencyList
}
export interface PopperChildrenProps {
@@ -78,11 +78,6 @@ interface PopperProps extends PositionCommonProps {
* Rtl attribute for the component.
*/
rtl?: boolean
-
- /**
- * Array of conditions to be met in order to trigger a subsequent render to reposition the elements.
- */
- positioningDependencies?: any[]
}
/**
@@ -98,7 +93,7 @@ const Popper: React.FunctionComponent = props => {
pointerRef,
position,
positionFixed,
- positioningDependencies,
+ positioningDependencies = [],
rtl,
targetRef,
} = props
@@ -112,9 +107,20 @@ const Popper: React.FunctionComponent = props => {
proposedPlacement,
)
+ const computedModifiers: PopperJS.Modifiers = React.useMemo(
+ () =>
+ offset && {
+ offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
+ keepTogether: { enabled: false },
+ },
+ [rtl, offset, position],
+ )
+
React.useEffect(
() => {
const handleUpdate = (data: PopperJS.Data) => {
+ // PopperJS performs computations that might update the computed placement: auto positioning, flipping the
+ // placement in case the popper box should be rendered at the edge of the viewport and does not fit
if (data.placement !== latestPlacement.current) {
latestPlacement.current = data.placement
setComputedPlacement(data.placement)
@@ -127,10 +133,7 @@ const Popper: React.FunctionComponent = props => {
eventsEnabled,
positionFixed,
modifiers: {
- ...(offset && {
- offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
- keepTogether: { enabled: false },
- }),
+ ...computedModifiers,
...modifiers,
arrow: {
enabled: !!pointerRefElement,
@@ -144,22 +147,22 @@ const Popper: React.FunctionComponent = props => {
popperRef.current = new PopperJS(targetRef.current, contentRef.current, options)
return () => popperRef.current.destroy()
},
- [eventsEnabled, modifiers, offset, position, positionFixed, rtl, proposedPlacement],
+ [computedModifiers, eventsEnabled, modifiers, positionFixed, proposedPlacement],
)
React.useEffect(
() => {
popperRef.current.scheduleUpdate()
},
- [...(positioningDependencies || []), computedPlacement],
+ [...positioningDependencies, computedPlacement],
)
- let processedChildren = children
- if (typeof children === 'function') {
- processedChildren = (children as PopperChildrenFn)({ placement: computedPlacement })
- }
+ const child =
+ typeof children === 'function'
+ ? (children as PopperChildrenFn)({ placement: computedPlacement })
+ : React.Children.only(children)
- return [{processedChildren as React.ReactElement}]
+ return [{child as React.ReactElement}]
}
Popper.defaultProps = {
diff --git a/packages/react/src/lib/positioner/index.ts b/packages/react/src/lib/positioner/index.ts
index a2545a4e75..3625f67ab7 100644
--- a/packages/react/src/lib/positioner/index.ts
+++ b/packages/react/src/lib/positioner/index.ts
@@ -1,6 +1,2 @@
-export type Position = 'above' | 'below' | 'before' | 'after'
-export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
-export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
-export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
-
+export * from './types'
export { default as Popper, PositionCommonProps, PopperChildrenProps } from './Popper'
diff --git a/packages/react/src/lib/positioner/positioningHelper.ts b/packages/react/src/lib/positioner/positioningHelper.ts
index 8a32f642e6..607e9feba0 100644
--- a/packages/react/src/lib/positioner/positioningHelper.ts
+++ b/packages/react/src/lib/positioner/positioningHelper.ts
@@ -1,6 +1,6 @@
import { Placement } from 'popper.js'
-import { Alignment, Position } from './index'
+import { Alignment, Position } from './types'
enum PlacementParts {
top = 'top',
diff --git a/packages/react/src/lib/positioner/types.ts b/packages/react/src/lib/positioner/types.ts
new file mode 100644
index 0000000000..08905a06ac
--- /dev/null
+++ b/packages/react/src/lib/positioner/types.ts
@@ -0,0 +1,4 @@
+export type Position = 'above' | 'below' | 'before' | 'after'
+export type Alignment = 'top' | 'bottom' | 'start' | 'end' | 'center'
+export const POSITIONS: Position[] = ['above', 'below', 'before', 'after']
+export const ALIGNMENTS: Alignment[] = ['top', 'bottom', 'start', 'end', 'center']
From 475191a4ceac05327695556786cca3f19111f2e3 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Mon, 20 May 2019 16:25:27 +0200
Subject: [PATCH 18/30] aligned with latest Popper API
---
.../src/components/Dropdown/Dropdown.tsx | 25 +++++++++----------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 04f2a0b33d..c3d2eb7d6b 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -655,19 +655,18 @@ class Dropdown extends AutoControlledComponent, Dropdo
eventsEnabled={open}
targetRef={this.selectedItemsRef}
positioningDependencies={[items.length]}
- children={() => (
-
- )}
- />
+ >
+
+
)
}
From a57bfc9c1ff2cfb94debd15bbfb330137d2a3e64 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Tue, 21 May 2019 22:56:54 +0200
Subject: [PATCH 19/30] changelog
---
CHANGELOG.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c85f0fcda9..96e75fba8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
+### Features
+- Add `align`, `position`, `offset` props for `Dropdown` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
+
## [v0.31.0](https://github.com/stardust-ui/react/tree/v0.31.0) (2019-05-21)
[Compare changes](https://github.com/stardust-ui/react/compare/v0.30.0...v0.31.0)
@@ -45,7 +48,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Export `qna` and `yammer` SVG icons for `Teams` theme @manindr ([#1325](https://github.com/stardust-ui/react/pull/1325))
- Add FontAwesome theme @layershifter ([#1337](https://github.com/stardust-ui/react/pull/1337))
- Add red color scheme in Teams theme @mnajdova ([#1353](https://github.com/stardust-ui/react/pull/1353))
-- Add `align`, `position`, `offset` props for `Dropdown` component @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
### Documentation
- Clearly identify Slots in DocSite @hughreeling ([#1292](https://github.com/stardust-ui/react/pull/1292))
From 02daf1518f69c2cec5c15150bd11f742fc9a519a Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 23 May 2019 16:55:46 +0200
Subject: [PATCH 20/30] fix type
---
packages/react/src/components/Dropdown/Dropdown.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index c3d2eb7d6b..bab6ea46d0 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -41,7 +41,7 @@ import { screenReaderContainerStyles } from '../../lib/accessibility/Styles/acce
import ListItem from '../List/ListItem'
import Icon, { IconProps } from '../Icon/Icon'
import Portal from '../Portal/Portal'
-import { ALIGNMENTS, POSITIONS, Popper, PositionCommonProps } from '../../lib/positioner'
+import { ALIGNMENTS, POSITIONS, Popper, PositioningProps } from '../../lib/positioner'
export interface DropdownSlotClassNames {
clearIndicator: string
@@ -57,7 +57,7 @@ export interface DropdownSlotClassNames {
export interface DropdownProps
extends UIComponentProps,
- PositionCommonProps {
+ PositioningProps {
/** The index of the currently active selected item, if dropdown has a multiple selection. */
activeSelectedIndex?: number
From ba3e29a062ea9e179b086f223dac3e397f9cefcf Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 23 May 2019 17:27:40 +0200
Subject: [PATCH 21/30] reverted popup example file
---
.../Usage/PopupExampleAsync.shorthand.tsx | 41 +++++++++----------
1 file changed, 20 insertions(+), 21 deletions(-)
diff --git a/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx b/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx
index 9df66b2797..860fcf915b 100644
--- a/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Usage/PopupExampleAsync.shorthand.tsx
@@ -1,31 +1,30 @@
import * as React from 'react'
-import { Button, Popup, Segment, Loader } from '@stardust-ui/react'
+import { Button, Popup, Segment } from '@stardust-ui/react'
-const AsyncDataLoader: React.FunctionComponent<{ onLoaded: Function }> = props => {
- const [data, setData] = React.useState()
+class AsyncDataLoader extends React.Component {
+ state = {
+ data: 'loading..',
+ }
- React.useEffect(() => {
+ componentDidMount() {
setTimeout(() => {
- setData(Hello from loaded data!)
- props.onLoaded()
+ this.setState({
+ data: Hello from loaded data!,
+ })
+ this.props.onLoaded()
}, 1000)
- }, [])
+ }
- return data
+ render() {
+ return this.state.data
+ }
}
-const PopupExampleAsync = () => {
- const [loading, setLoading] = React.useState(true)
- return (
- {
- if (!data.open) setLoading(true)
- }}
- trigger={}
- positioningDependencies={[loading]}
- content={{ content: setLoading(false)} /> }}
- />
- )
-}
+const PopupExampleAsync = () => (
+ }
+ renderContent={updatePosition => ({ content: })}
+ />
+)
export default PopupExampleAsync
From a2f7b6a84ad537b606c73cd2fef85242ad1ad503 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Thu, 23 May 2019 17:46:49 +0200
Subject: [PATCH 22/30] fix bad merge
---
CHANGELOG.md | 1 -
packages/docs-components/src/knobs/useSelectKnob.ts | 6 +++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba3bac7ad9..71af3f5c35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,7 +38,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Aligned focus styles for `Chat.Message` component with latest Teams theme design @Bugaa92 ([#1269](https://github.com/stardust-ui/react/pull/1269))
- Fix styles for `fill` prop of `Flex` @kuzhelov ([#1352](https://github.com/stardust-ui/react/pull/1352))
- FontAwesome icons is not part of Teams theme more @layershifter ([#1337](https://github.com/stardust-ui/react/pull/1337))
-- Replace `react-popper` package with custom `Popper` component @Bugaa92 ([#1358](https://github.com/stardust-ui/react/pull/1358))
### Fixes
- Fixed `Flex.Item` children prop type @mnajdova ([#1320](https://github.com/stardust-ui/react/pull/1320))
diff --git a/packages/docs-components/src/knobs/useSelectKnob.ts b/packages/docs-components/src/knobs/useSelectKnob.ts
index a8d4d0a95d..4caf07ab60 100644
--- a/packages/docs-components/src/knobs/useSelectKnob.ts
+++ b/packages/docs-components/src/knobs/useSelectKnob.ts
@@ -2,6 +2,10 @@ import { UseKnobOptions } from './types'
import useKnob from './useKnob'
const useSelectKnob = (options: UseKnobOptions) =>
- useKnob({ initialValue: '' as T, type: 'select', ...options })
+ useKnob({
+ initialValue: '' as T,
+ type: 'select',
+ ...options,
+ })
export default useSelectKnob
From 56712f0055dc2d97c6f1cad40e784399a07cd428 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Tue, 28 May 2019 12:57:57 +0200
Subject: [PATCH 23/30] removed default padding from popper
---
packages/react/src/lib/positioner/Popper.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
index c6e6715a15..c4ecffa888 100644
--- a/packages/react/src/lib/positioner/Popper.tsx
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -58,6 +58,7 @@ const Popper: React.FunctionComponent = props => {
eventsEnabled,
positionFixed,
modifiers: {
+ preventOverflow: { padding: 0 },
...computedModifiers,
...modifiers,
arrow: {
From 2f30ba1b625001ca09be77b655605e53ce2da638 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Tue, 28 May 2019 14:54:47 +0200
Subject: [PATCH 24/30] improved dropdown positioning and removed dead styles
---
packages/react/src/components/Dropdown/Dropdown.tsx | 4 +++-
.../src/themes/teams/components/Dropdown/dropdownStyles.ts | 4 ----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 5dca713c5d..495706ca39 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -224,6 +224,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
private inputRef = React.createRef()
private listRef = React.createRef()
private selectedItemsRef = React.createRef()
+ private containerRef = React.createRef()
static displayName = 'Dropdown'
@@ -433,6 +434,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
return (
[
]
@@ -653,7 +655,7 @@ class Dropdown extends AutoControlledComponent
, Dropdo
offset={offset}
rtl={rtl}
eventsEnabled={open}
- targetRef={this.selectedItemsRef}
+ targetRef={this.containerRef}
positioningDependencies={[items.length]}
>
({
outline: 0,
- position: 'absolute',
borderRadius: v.listBorderRadius,
borderStyle: 'solid',
borderWidth: p.open ? v.listBorderWidth : '0px',
@@ -153,8 +151,6 @@ const dropdownStyles: ComponentSlotStylesInput
Date: Wed, 29 May 2019 10:34:41 +0200
Subject: [PATCH 25/30] Update
docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
Co-Authored-By: Oleksandr Fediashov
---
.../Popup/Variations/PopupExamplePosition.shorthand.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
index bd142a32d8..141b074613 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
@@ -67,7 +67,7 @@ const positionAndAlignValues: PositionAndAlign[] = [
'after-bottom',
]
-const icons: { [key in Position]: string } = {
+const icons: Record = {
above: 'arrow circle up',
below: 'arrow circle down',
before: 'arrow circle left',
From a096221f2c70d7a250315a6197cd271c29507c6c Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Wed, 29 May 2019 11:18:21 +0200
Subject: [PATCH 26/30] removed toggle icon logic
---
packages/react/src/components/Dropdown/Dropdown.tsx | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx
index 495706ca39..119bf9bdb8 100644
--- a/packages/react/src/components/Dropdown/Dropdown.tsx
+++ b/packages/react/src/components/Dropdown/Dropdown.tsx
@@ -472,7 +472,7 @@ class Dropdown extends AutoControlledComponent, Dropdo
: Icon.create(toggleIndicator, {
defaultProps: {
className: Dropdown.slotClassNames.toggleIndicator,
- name: this.getToggleIndicatorIconName(),
+ name: open ? 'stardust-arrow-up' : 'stardust-arrow-down',
styles: styles.toggleIndicator,
},
overrideProps: (predefinedProps: IconProps) => ({
@@ -1296,15 +1296,6 @@ class Dropdown extends AutoControlledComponent, Dropdo
this.setState({ startingString: '' })
}, Dropdown.charKeyPressedCleanupTime)
}
-
- private getToggleIndicatorIconName = (): string => {
- const { open } = this.state
- const positionedAbove = this.props.position === 'above'
-
- return (open && !positionedAbove) || (!open && positionedAbove)
- ? 'stardust-arrow-up'
- : 'stardust-arrow-down'
- }
}
Dropdown.slotClassNames = {
From 0e432ac710397380a9cef19f1ba98e050d79c8d9 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Wed, 29 May 2019 12:37:58 +0200
Subject: [PATCH 27/30] addressed final comments
---
.../DropdownExamplePosition.shorthand.tsx | 12 ++---------
.../PopupExamplePosition.shorthand.tsx | 20 +++----------------
.../Popup/Variations/PopupExamplePosition.tsx | 20 +++----------------
packages/react/src/lib/positioner/Popper.tsx | 1 +
.../components/Dropdown/dropdownVariables.ts | 6 +++++-
.../components/Dropdown/dropdownStyles.ts | 17 +++++-----------
.../components/Dropdown/dropdownVariables.ts | 14 +++++++++----
7 files changed, 29 insertions(+), 61 deletions(-)
diff --git a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
index 04304568e7..22971246ec 100644
--- a/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Dropdown/Variations/DropdownExamplePosition.shorthand.tsx
@@ -8,7 +8,7 @@ const inputItems = ['Bruce Wayne', 'Natasha Romanoff', 'Steven Strange']
const DropdownExamplePosition = () => {
const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const [positionAndAlign] = useSelectKnob({
+ const [positionAndAlign] = useSelectKnob({
name: 'position-align',
initialValue: 'below',
values: positionAndAlignValues,
@@ -31,15 +31,7 @@ const DropdownExamplePosition = () => {
export default DropdownExamplePosition
-type PositionAndAlign =
- | 'above'
- | 'below'
- | 'before-top'
- | 'before-bottom'
- | 'after-top'
- | 'after-bottom'
-
-const positionAndAlignValues: PositionAndAlign[] = [
+const positionAndAlignValues = [
'above',
'below',
'before-top',
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
index 141b074613..4b8b63911d 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
@@ -6,7 +6,7 @@ import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
const PopupExamplePosition = () => {
const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const [positionAndAlign] = useSelectKnob({
+ const [positionAndAlign] = useSelectKnob({
name: 'position-align-s',
initialValue: 'above-start',
values: positionAndAlignValues,
@@ -38,21 +38,7 @@ const PopupExamplePosition = () => {
export default PopupExamplePosition
-type PositionAndAlign =
- | 'above-start'
- | 'above-center'
- | 'above-end'
- | 'below-start'
- | 'below-center'
- | 'below-end'
- | 'before-top'
- | 'before-center'
- | 'before-bottom'
- | 'after-top'
- | 'after-center'
- | 'after-bottom'
-
-const positionAndAlignValues: PositionAndAlign[] = [
+const positionAndAlignValues = [
'above-start',
'above-center',
'above-end',
@@ -74,7 +60,7 @@ const icons: Record = {
after: 'arrow circle right',
}
-const paddings: Record = {
+const paddings: Record = {
'above-start': '5px 42px 18px 5px',
'above-center': '5px 5px 18px 5px',
'above-end': '5px 5px 18px 42px',
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
index d5f498dbfe..fe8c8fbf5c 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
@@ -6,7 +6,7 @@ import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
const PopupExamplePosition = () => {
const [open] = useBooleanKnob({ name: 'open', initialValue: true })
- const [positionAndAlign] = useSelectKnob({
+ const [positionAndAlign] = useSelectKnob({
name: 'position-align-c',
initialValue: 'above-start',
values: positionAndAlignValues,
@@ -39,21 +39,7 @@ const PopupExamplePosition = () => {
export default PopupExamplePosition
-type PositionAndAlign =
- | 'above-start'
- | 'above-center'
- | 'above-end'
- | 'below-start'
- | 'below-center'
- | 'below-end'
- | 'before-top'
- | 'before-center'
- | 'before-bottom'
- | 'after-top'
- | 'after-center'
- | 'after-bottom'
-
-const positionAndAlignValues: PositionAndAlign[] = [
+const positionAndAlignValues = [
'above-start',
'above-center',
'above-end',
@@ -75,7 +61,7 @@ const icons: { [key in Position]: string } = {
after: 'arrow circle right',
}
-const paddings: Record = {
+const paddings: Record = {
'above-start': '5px 42px 18px 5px',
'above-center': '5px 5px 18px 5px',
'above-end': '5px 5px 18px 42px',
diff --git a/packages/react/src/lib/positioner/Popper.tsx b/packages/react/src/lib/positioner/Popper.tsx
index 677c7239b9..0cd54ec2f0 100644
--- a/packages/react/src/lib/positioner/Popper.tsx
+++ b/packages/react/src/lib/positioner/Popper.tsx
@@ -50,6 +50,7 @@ const Popper: React.FunctionComponent = props => {
const modifiers: PopperJS.Modifiers = _.merge(
{ preventOverflow: { padding: 0 } },
+ { flip: { padding: 0 } },
/**
* When the popper box is placed in the context of a scrollable element, we need to set
* preventOverflow.escapeWithReference to true and flip.boundariesElement to 'scrollParent' (default is 'viewport')
diff --git a/packages/react/src/themes/teams-high-contrast/components/Dropdown/dropdownVariables.ts b/packages/react/src/themes/teams-high-contrast/components/Dropdown/dropdownVariables.ts
index da2447e8a1..9738d0c45a 100644
--- a/packages/react/src/themes/teams-high-contrast/components/Dropdown/dropdownVariables.ts
+++ b/packages/react/src/themes/teams-high-contrast/components/Dropdown/dropdownVariables.ts
@@ -5,16 +5,20 @@ export interface DropdownVariablesHC extends DropdownVariables {
borderColorHover: string
}
+const _3px_asRem = pxToRem(3)
+
export default (siteVars): Partial => ({
backgroundColor: siteVars.colors.black,
borderColor: siteVars.colors.white,
- borderBottomRadius: pxToRem(3),
+ containerBorderRadius: _3px_asRem,
+ openAboveContainerBorderRadius: `0 0 ${_3px_asRem} ${_3px_asRem}`,
borderWidth: `1px`,
backgroundColorHover: siteVars.colors.black,
borderColorHover: siteVars.accessibleYellow,
borderColorFocus: siteVars.accessibleCyan,
color: siteVars.colors.white,
selectedItemColor: siteVars.colors.white,
+ belowListBorderRadius: `0 0 ${_3px_asRem} ${_3px_asRem}`,
listBackgroundColor: siteVars.colors.black,
listBorderColor: siteVars.colors.white,
listBoxShadow: undefined,
diff --git a/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts b/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts
index 737032bdb9..d4d29055b8 100644
--- a/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts
+++ b/packages/react/src/themes/teams/components/Dropdown/dropdownStyles.ts
@@ -56,16 +56,6 @@ const getWidth = (p: DropdownPropsAndState, v: DropdownVariables): string => {
return v.width
}
-const getOpenBorderStyles = (attached: 'top' | 'bottom', v: DropdownVariables) =>
- attached === 'top'
- ? `0 0 ${v.borderBottomRadius} ${v.borderBottomRadius}`
- : `${v.borderTopRadius} ${v.borderTopRadius} 0 0`
-
-const getContainerBorderRadius = (p: DropdownPropsAndState, v: DropdownVariables) => {
- if (p.open) return getOpenBorderStyles(p.position === 'below' ? 'bottom' : 'top', v)
- return `${v.borderTopRadius} ${v.borderTopRadius} ${v.borderBottomRadius} ${v.borderBottomRadius}`
-}
-
const dropdownStyles: ComponentSlotStylesInput = {
root: ({ props: p }): ICSSInJSStyle => ({
...(p.inline && { display: 'inline-flex' }),
@@ -82,9 +72,11 @@ const dropdownStyles: ComponentSlotStylesInput ({
outline: 0,
- borderRadius: getOpenBorderStyles(p.position === 'below' ? 'top' : 'bottom', v),
borderStyle: 'solid',
borderWidth: p.open ? v.listBorderWidth : '0px',
borderColor: v.listBorderColor,
@@ -156,6 +147,8 @@ const dropdownStyles: ComponentSlotStylesInput pxToRem(v))
export default (siteVars): DropdownVariables => ({
backgroundColor: siteVars.colors.grey[100],
backgroundColorHover: siteVars.colors.grey[150],
- borderBottomRadius: _2px_asRem,
borderColor: 'transparent',
borderColorFocus: siteVars.colors.brand[600],
- borderTopRadius: _3px_asRem,
borderWidth: '0px',
+ containerBorderRadius: `${_3px_asRem} ${_3px_asRem} ${_2px_asRem} ${_2px_asRem}`,
+ openAboveContainerBorderRadius: `0 0 ${_2px_asRem} ${_2px_asRem}`,
+ openBelowContainerBorderRadius: `${_3px_asRem} ${_3px_asRem} 0 0`,
searchBorderBottomWidth: pxToRem(2),
color: siteVars.bodyColor,
selectedItemColor: siteVars.bodyColor,
comboboxPaddingButton: `0 ${_12px_asRem}`,
comboboxFlexBasis: pxToRem(50),
+ aboveListBorderRadius: `${_3px_asRem} ${_3px_asRem} 0 0`,
+ belowListBorderRadius: `0 0 ${_2px_asRem} ${_2px_asRem}`,
listBackgroundColor: siteVars.colors.white,
listBorderColor: 'transparent',
listBorderWidth: '0px',
From 18e936591d5b10b9716b4e49268b2687dee3463a Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Wed, 29 May 2019 13:35:34 +0200
Subject: [PATCH 28/30] new changelog entry
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e596d43652..8abfd8c771 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### BREAKING CHANGES
- Remove convoluted conditions from component's element type calculations @kuzhelov ([#1396](https://github.com/stardust-ui/react/pull/1396))
+- Replace `Dropdown` variables: `borderRadius` with `containerBorderRadius`, `openBorderRadius` with `openAboveContainerBorderRadius` and `openBelowContainerBorderRadius`, `listBorderRadius` with `aboveListBorderRadius` and `belowListBorderRadius` @Bugaa92 ([#1312](https://github.com/stardust-ui/react/pull/1312))
### Fixes
- ESC key pressed on a trigger element should propagate event if `Popup` is closed @sophieH29 ([#1373](https://github.com/stardust-ui/react/pull/1373))
From f04127713cca545183fdffce8d38d9b11b5d1734 Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Wed, 29 May 2019 14:14:00 +0200
Subject: [PATCH 29/30] addressed final comments
---
.../components/Popup/Variations/PopupExamplePosition.tsx | 2 +-
packages/react/src/lib/positioner/positioningHelper.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
index fe8c8fbf5c..30b14b5a13 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
@@ -54,7 +54,7 @@ const positionAndAlignValues = [
'after-bottom',
]
-const icons: { [key in Position]: string } = {
+const icons: Record = {
above: 'arrow circle up',
below: 'arrow circle down',
before: 'arrow circle left',
diff --git a/packages/react/src/lib/positioner/positioningHelper.ts b/packages/react/src/lib/positioner/positioningHelper.ts
index b128a7d7a3..c14a7e042a 100644
--- a/packages/react/src/lib/positioner/positioningHelper.ts
+++ b/packages/react/src/lib/positioner/positioningHelper.ts
@@ -12,14 +12,14 @@ enum PlacementParts {
center = '',
}
-const getPositionMap = (rtl: boolean): { [key in Position]: PlacementParts } => ({
+const getPositionMap = (rtl: boolean): Record => ({
above: PlacementParts.top,
below: PlacementParts.bottom,
before: rtl ? PlacementParts.right : PlacementParts.left,
after: rtl ? PlacementParts.left : PlacementParts.right,
})
-const getAlignmentMap = (rtl: boolean): { [key in Alignment]: PlacementParts } => ({
+const getAlignmentMap = (rtl: boolean): Record => ({
start: rtl ? PlacementParts.end : PlacementParts.start,
end: rtl ? PlacementParts.start : PlacementParts.end,
top: PlacementParts.start,
From e23e9563178eb37da8b1435d9ed9081faaba466b Mon Sep 17 00:00:00 2001
From: Alexandru Buliga
Date: Wed, 29 May 2019 14:50:38 +0200
Subject: [PATCH 30/30] final touches in examples and mentions prototype
---
.../Popup/Variations/PopupExamplePosition.shorthand.tsx | 2 +-
.../components/Popup/Variations/PopupExamplePosition.tsx | 2 +-
docs/src/prototypes/mentions/MentionsDropdown.tsx | 3 ++-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
index 4b8b63911d..acee076f50 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.shorthand.tsx
@@ -4,7 +4,7 @@ import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
const PopupExamplePosition = () => {
- const [open] = useBooleanKnob({ name: 'open', initialValue: true })
+ const [open] = useBooleanKnob({ name: 'open-s', initialValue: true })
const [positionAndAlign] = useSelectKnob({
name: 'position-align-s',
diff --git a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
index 30b14b5a13..fb6f7d86f6 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExamplePosition.tsx
@@ -4,7 +4,7 @@ import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
import { useBooleanKnob, useSelectKnob } from '@stardust-ui/docs-components'
const PopupExamplePosition = () => {
- const [open] = useBooleanKnob({ name: 'open', initialValue: true })
+ const [open] = useBooleanKnob({ name: 'open-c', initialValue: true })
const [positionAndAlign] = useSelectKnob({
name: 'position-align-c',
diff --git a/docs/src/prototypes/mentions/MentionsDropdown.tsx b/docs/src/prototypes/mentions/MentionsDropdown.tsx
index 3913c1ebb4..51eca93f86 100644
--- a/docs/src/prototypes/mentions/MentionsDropdown.tsx
+++ b/docs/src/prototypes/mentions/MentionsDropdown.tsx
@@ -22,9 +22,10 @@ const MentionsDropdown: React.FunctionComponent = props
(