Skip to content

Commit

Permalink
[components] Add a general Popover dialog (#297)
Browse files Browse the repository at this point in the history
[components] Adding story for Popover Dialog. (and linting story)

[components] Using LayerStack on all popover-like components

[components] Pretty up Searchable select, and support all popovers

[form-builder] Using FullScreen dialog on fullscreen block-editor

[form-builder] Using popover to make "Confirm delete" in array

[components] Button: Only add padding on icon when children is text

[components] Not use let on defaultbutton
  • Loading branch information
kristofferjs authored and bjoerge committed Nov 7, 2017
1 parent 8033a3c commit 4f61ac8
Show file tree
Hide file tree
Showing 21 changed files with 682 additions and 308 deletions.
8 changes: 8 additions & 0 deletions packages/@sanity/components/sanity.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@
"name": "part:@sanity/components/dialogs/confirm",
"description": "Confirm dialog"
},
{
"name": "part:@sanity/components/dialogs/popover",
"description": "Popover dialog. Points an arrow to parent element"
},
{
"name": "part:@sanity/components/snackbar/default",
"description": "Shows messages with an action and a timeout."
Expand Down Expand Up @@ -519,6 +523,10 @@
"implements": "part:@sanity/components/dialogs/fullscreen",
"path": "dialogs/FullscreenDialog.js"
},
{
"implements": "part:@sanity/components/dialogs/popover",
"path": "dialogs/PopOver.js"
},
{
"implements": "part:@sanity/components/fieldsets/default",
"path": "fieldsets/DefaultFieldset.js"
Expand Down
6 changes: 4 additions & 2 deletions packages/@sanity/components/src/buttons/DefaultButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export default class DefaultButton extends React.Component {
this.state.recentlyHovered ? styles.recentlyHovered : styles.notRecentlyHovered
].filter(Boolean).join(' ')

const padContent = (Icon && children && (typeof children === 'string'))

return (
<button
{...rest}
Expand All @@ -83,10 +85,10 @@ export default class DefaultButton extends React.Component {
loading && <span className={styles.spinner}><Spinner inline /></span>
}
{
Icon && <span className={styles.iconContainer}><Icon className={styles.icon} /></span>
Icon && <Icon className={styles.icon} />
}
{
children && <span className={styles.content}>{children}</span>
children && <span className={padContent ? styles.contentWithPad : styles.content}>{children}</span>
}
{
ripple && !disabled && <Ink duration={200} opacity={0.10} radius={200} />
Expand Down
2 changes: 1 addition & 1 deletion packages/@sanity/components/src/buttons/DropDownButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ class DropDownButton extends React.PureComponent {

handleClickOutside = event => {
this.setState({menuOpened: false})
event.stopPropagation()
}

handleClose = () => {
Expand Down Expand Up @@ -161,6 +160,7 @@ class DropDownButton extends React.PureComponent {
useOverlay={false}
addPadding={false}
scrollIntoView={false}
onClose={this.handleClose}
>
<div
ref={this.setMenuElement}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,10 @@
.content {
display: inline-block;
vertical-align: middle;
}

@nest .hasIcon & {
margin-left: 0.5em;
}
.contentWithPad {
margin-left: 0.5em;
}

.icon {
Expand Down
2 changes: 1 addition & 1 deletion packages/@sanity/components/src/dialogs/ConfirmDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import styles from './styles/ConfirmDialog.css'
import Button from 'part:@sanity/components/buttons/default'
import Portal from 'react-portal'

export default class DefaultDialog extends React.Component {
export default class DefaultDialog extends React.PureComponent {
static propTypes = {
color: PropTypes.oneOf(['warning', 'success', 'danger', 'info']),
confirmColor: PropTypes.oneOf(['success', 'danger']),
Expand Down
2 changes: 1 addition & 1 deletion packages/@sanity/components/src/dialogs/DefaultDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import styles from 'part:@sanity/components/dialogs/default-style'
import Button from 'part:@sanity/components/buttons/default'
import Portal from 'react-portal'

export default class DefaultDialog extends React.Component {
export default class DefaultDialog extends React.PureComponent {
static propTypes = {
kind: PropTypes.oneOf(['default', 'warning', 'success', 'danger', 'info']),
className: PropTypes.string,
Expand Down
54 changes: 28 additions & 26 deletions packages/@sanity/components/src/dialogs/FullscreenDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default class FullScreenDialog extends React.PureComponent {
.join(' ')

return (
<Portal closeOnEsc={this.isClosable()} isOpened={isOpen}>
<Portal isOpened={isOpen}>
<div className={classNames}>
{
onClose && (
Expand All @@ -84,31 +84,33 @@ export default class FullScreenDialog extends React.PureComponent {
{this.props.children}
<div className={styles.actions}>
{
actions.length > 0 && <div className={styles.functions}>
{
actions.map((action, i) => {
return (
<Button
key={i}
onClick={this.handleActionClick}
data-action-index={i}
color={color === 'default' ? action.color : 'white'}
disabled={action.disabled}
inverted={!action.secondary}
kind={action.kind}
autoFocus={action.autoFocus}
className={`
${styles.button}
${styles[`button_${action.kind}`] || styles.button}
`
}
>
{action.title}
</Button>
)
})
}
</div>
actions.length > 0 && (
<div className={styles.functions}>
{
actions.map((action, i) => {
return (
<Button
key={i}
onClick={this.handleActionClick}
data-action-index={i}
color={color === 'default' ? action.color : 'white'}
disabled={action.disabled}
inverted={!action.secondary}
kind={action.kind}
autoFocus={action.autoFocus}
className={`
${styles.button}
${styles[`button_${action.kind}`] || styles.button}
`
}
>
{action.title}
</Button>
)
})
}
</div>
)
}
</div>
</div>
Expand Down
181 changes: 181 additions & 0 deletions packages/@sanity/components/src/dialogs/PopOver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import PropTypes from 'prop-types'
import React from 'react'
import styles from './styles/PopOver.css'
import CloseIcon from 'part:@sanity/base/close-icon'
import StickyPortal from 'part:@sanity/components/portal/sticky'
import tryFindScrollContainer from '../utilities/tryFindScrollContainer'

const PADDING = 5

export default class EditItemPopOver extends React.Component {

static propTypes = {
children: PropTypes.node.isRequired,
onClose: PropTypes.func,
isOpen: PropTypes.bool,
color: PropTypes.oneOf(['default', 'danger', 'success', 'warning', 'info']),
scrollContainer: PropTypes.object,
onClickOutside: PropTypes.func,
useOverlay: PropTypes.bool
}

static defaultProps = {
color: 'default',
scrollContainer: undefined,
onClose() {}, // eslint-disable-line
onClickOutside() {},
isOpen: true,
useOverlay: true
}

state = {
arrowLeft: 0,
popoverLeft: 0,
scrollContainer: undefined,
isResizing: false
}

componentDidMount() {
const {
scrollContainer
} = this.props

if (!this._rootElement) {
// console.error('no root element')
}

if (scrollContainer) {
this.setScrollContainerElement(scrollContainer)
} else {
this.setScrollContainerElement(tryFindScrollContainer(this._rootElement))
}
}

setScrollContainerElement = element => {
this.setState({
scrollContainer: element
})
}

handleClose = () => {
this.props.onClose()
}

handleKeyDown = event => {
if (event.key == 'Escape') {
this.handleClose()
}
}

setArrowElement = element => {
this._arrowElement = element
}

setContentElement = element => {
this._contentElement = element
}

setPopoverInnerElement = element => {
this._popOverInnerElement = element
}

setRootElement = element => {
this._rootElement = element
}

handlePortalResize = dimensions => {
if (!this._popOverInnerElement) {
return
}

const {
rootLeft,
availableHeight,
availableWidth
} = dimensions

const width = this._popOverInnerElement.offsetWidth

let popoverLeft = rootLeft - (width / 2)

if (availableWidth < (rootLeft + (width / 2))) {
popoverLeft = availableWidth - width - PADDING
}

this.setState({
popoverLeft: popoverLeft,
availableHeight: availableHeight,
arrowLeft: rootLeft
})
}

render() {
const {
children,
isOpen,
color,
useOverlay
} = this.props


const {
popoverLeft,
arrowLeft,
availableHeight,
scrollContainer,
} = this.state

return (
<div style={{display: 'span'}} ref={this.setRootElement}>
<StickyPortal
isOpen={isOpen}
scrollContainer={scrollContainer}
onClickOutside={this.props.onClickOutside}
onResize={this.handlePortalResize}
onClick={this.handleClick}
useOverlay={useOverlay}
onClose={this.handleClose}
>
<div
ref={this.setPopoverInnerElement}
className={`
${styles.root}
${color === 'danger' ? styles.colorDanger : ''}
${color === 'warning' ? styles.colorWarning : ''}
${color === 'info' ? styles.colorInfo : ''}
${color === 'success' ? styles.colorSuccess : ''}
`}
>
<div
className={styles.arrow}
ref={this.setArrowElement}
style={{
left: `${arrowLeft}px`
}}
/>
<div
className={styles.popover}
style={{
left: `${popoverLeft}px`
}}
>
<button className={styles.close} type="button" onClick={this.handleClose}>
<CloseIcon />
</button>

<div
ref={this.setContentElement}
className={styles.content}
style={{
maxHeight: `${availableHeight - 16}px`
}}
>
{children}
</div>
</div>
</div>
</StickyPortal>
</div>
)
}
}

0 comments on commit 4f61ac8

Please sign in to comment.