Skip to content

Conversation

@jeroenransijn
Copy link
Contributor

This PR makes use of the createContext API in React 16.3.0 to allow automatic stacking of components.

This is a breaking change and will have to be released as v4.

Changes

  • Use React 16.3.0
  • Use createContext API
  • Export StackingContext from evergreen-ui
  • Export Stack component from evergreen-ui
  • Export StackingOrder object with z-index presets from evergreen-ui.
  • Remove zIndex props from Popover, Combobox and Positioner.

StackingContext

The StackingContext is a React context with a default value (z-index) of 10. The StackingContext is currently only used within the Stack component within Evergreen.

Stack component

The Stack component uses the StackingContext which accepts a function as children. That function takes in the zIndex and should return a React node:

static propTypes = {
  /**
   * Function that takes the current z-index and returns a React Node.
   * (zIndex) => ReactNode.
   */
  children: PropTypes.func.isRequired,

  /**
   * Set the value of the stack. This will increment for children.
   */
  value: PropTypes.number
}

Inside of the render function the Stack component first looks for the current value. Passing a value to the component will make sure the highest of the two are used. This is useful because Overlays start at a z-index of 20. See more info below about StackingOrder. The Stack component will pass the current value to the current children, and increment the value for the next consumer.

render() {
  const { children, value } = this.props
  return (
    <StackingContext.Consumer>
      {previousValue => {
        const currentValue = Math.max(value, previousValue)
        const nextValue = currentValue + 1
        return (
          <StackingContext.Provider value={nextValue}>
            {children(currentValue)}
          </StackingContext.Provider>
        )
      }}
    </StackingContext.Consumer>
  )
}

Stack component usage

In most cases Stack will be an internal component, we are exposing it if you want to build custom components on top of this logic.

<Stack value={StackingOrder.POSITIONER}>
  {zIndex => {
    return (
      /* ... code omitted */
    )
  }}
</Stack>

StackingOrder

The values here are somewhat random, the reason why POSITIONER and OVERLAY are 10 apart is that in between the Stack component can increment the z-index — giving a head room of 10 z-indexes.

/**
 * Stacking order contains z-index values that are used through.
 * Note that the Stack component might increase the z-index for certain components.
 */
export default {
  /**
   * Used for focused buttons and controls.
   */
  FOCUSED: 2,

  /**
   * Used as the default for the StackingContext.
   */
  STACKING_CONTEXT: 5,

  /**
   * Used as the default for the Positioner.
   */
  POSITIONER: 10,

  /**
   * Used for the Overlay and everything that's inside such as Dialog + SideSheet.
   */
  OVERLAY: 20,

  /**
   * Used for the toasts in the toaster. Appears on top of everything else.
   */
  TOASTER: 30
}

See Example

I tweeted about this with a video attached. Click the link below to see.

React.createContext is a great solution for solving automatic stacking order when using portals for modal dialogs, popovers, panels and sheets. #reactjs pic.twitter.com/9RImVl2ISV

— Jeroen Ransijn 🌲 (@Jeroen_Ransijn) April 17, 2018

Stuff that came up

When I was doing this PR, I realized there is an issue with the getPosition for the Positioner that creates an infinite loop when there is not enough space on the top or the bottom of the screen. I will fix this outside of the scope of this PR so it will be included in v3 as well.

Another issue that arose from creating examples is when a Popover contains a Combobox — which means another Popover. The Popover closes when trying to select an item from the Combobox. This is because the Popover inside the Popover does not live within the same tree. This should be fixed in a different PR as well.

position="absolute"
top="50%"
zIndex={3}
zIndex={StackingOrder.FOCUSED + 1}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why +1?

/**
* Set the value of the stack. This will increment for children.
*/
value: PropTypes.number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't value be required?

const currentValue = Math.max(undefined, previousValue)
const nextValue = currentValue + 1

nextValue === NaN

@jeroenransijn jeroenransijn mentioned this pull request Jul 3, 2018
@jeroenransijn
Copy link
Contributor Author

This is in v4.

@Rowno Rowno deleted the fix-stacking branch July 17, 2018 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants