Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Popover] Implement ability to pass coordinates as anchor #9004

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/src/pages/demos/popovers/AnchorPlayground.js
Expand Up @@ -10,6 +10,7 @@ import Grid from 'material-ui/Grid';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import Popover from 'material-ui/Popover';
import Input, { InputLabel } from 'material-ui/Input';

const styles = theme => ({
button: {
Expand All @@ -28,6 +29,9 @@ class AnchorPlayground extends React.Component {
anchorOriginHorizontal: 'center',
transformOriginVertical: 'top',
transformOriginHorizontal: 'center',
positionTop: 200, // Just so the popover can be spotted more easily
positionLeft: 400, // Same as above
anchorReference: 'anchorEl',
};

handleChange = key => (event, value) => {
Expand All @@ -36,6 +40,12 @@ class AnchorPlayground extends React.Component {
});
};

handleNumberInputChange = key => event => {
this.setState({
[key]: parseInt(event.target.value, 10),
});
};

handleClickButton = () => {
this.setState({
open: true,
Expand All @@ -60,6 +70,9 @@ class AnchorPlayground extends React.Component {
anchorOriginHorizontal,
transformOriginVertical,
transformOriginHorizontal,
positionTop,
positionLeft,
anchorReference,
} = this.state;

return (
Expand All @@ -77,6 +90,8 @@ class AnchorPlayground extends React.Component {
<Popover
open={open}
anchorEl={anchorEl}
anchorReference={anchorReference}
anchorPosition={{ top: positionTop, left: positionLeft }}
onRequestClose={this.handleRequestClose}
anchorOrigin={{
vertical: anchorOriginVertical,
Expand All @@ -90,6 +105,46 @@ class AnchorPlayground extends React.Component {
<Typography className={classes.typography}>The content of the Popover.</Typography>
</Popover>
<Grid container>
<Grid item xs={12} sm={6}>
<FormControl component="fieldset">
<FormLabel component="legend">anchorReference</FormLabel>
<RadioGroup
row
aria-label="anchorReference"
name="anchorReference"
value={this.state.anchorReference}
onChange={this.handleChange('anchorReference')}
>
<FormControlLabel value="anchorEl" control={<Radio />} label="anchorEl" />
<FormControlLabel
value="anchorPosition"
control={<Radio />}
label="anchorPosition"
/>
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} sm={6}>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="position-top">anchorPosition.top</InputLabel>
<Input
id="position-top"
type="number"
value={this.state.positionTop}
onChange={this.handleNumberInputChange('positionTop')}
/>
</FormControl>
&nbsp;
<FormControl className={classes.formControl}>
<InputLabel htmlFor="position-left">anchorPosition.left</InputLabel>
<Input
id="position-left"
type="number"
value={this.state.positionLeft}
onChange={this.handleNumberInputChange('positionLeft')}
/>
</FormControl>
</Grid>
<Grid item xs={12} sm={6}>
<FormControl component="fieldset">
<FormLabel component="legend">anchorOrigin.vertical</FormLabel>
Expand Down
4 changes: 4 additions & 0 deletions docs/src/pages/demos/popovers/popovers.md
Expand Up @@ -9,6 +9,10 @@ A `Popover` can be used to display some content on top of another.
## Anchor playground

Use the radio buttons to adjust the `anchorOrigin` and `transformOrigin` positions.
You can also set the `anchorReference` to `anchorPosition` or `anchorEl`.
When it is `anchorPosition`, the component will, instead of `anchorEl`,
refer to the `anchorPosition` prop which you can adjust to set
the position of the popover.

{{demo='pages/demos/popovers/AnchorPlayground.js'}}

Expand Down
1 change: 1 addition & 0 deletions pages/api/collapse.md
Expand Up @@ -15,6 +15,7 @@ filename: /src/transitions/Collapse.js
| <span style="color: #31a148">children *</span> | Node | | The content node to be collapsed. |
| classes | Object | | Useful to extend the style applied to components. |
| collapsedHeight | string | '0px' | The height of the container when collapsed. |
| component | ElementType | 'div' | The component used for the root node. Either a string to use a DOM element or a component. The default value is a `button`. |
| <span style="color: #31a148">in *</span> | boolean | | If `true`, the component will transition in. |
| timeout | union:&nbsp;number&nbsp;&#124;<br>&nbsp;{ enter?: number, exit?: number }&nbsp;&#124;<br>&nbsp;'auto'<br> | duration.standard | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object.<br>Set to 'auto' to automatically calculate transition time based on height. |

Expand Down
1 change: 1 addition & 0 deletions pages/api/list-subheader.md
Expand Up @@ -15,6 +15,7 @@ filename: /src/List/ListSubheader.js
| children | Node | | The content of the component. |
| classes | Object | | Useful to extend the style applied to components. |
| color | union:&nbsp;'default'&nbsp;&#124;<br>&nbsp;'primary'&nbsp;&#124;<br>&nbsp;'inherit'<br> | 'default' | The color of the component. It's using the theme palette when that makes sense. |
| component | ElementType | 'li' | The component used for the root node. Either a string to use a DOM element or a component. The default value is a `button`. |
| disableSticky | boolean | false | If `true`, the List Subheader will not stick to the top during scroll. |
| inset | boolean | false | If `true`, the List Subheader will be indented. |

Expand Down
6 changes: 4 additions & 2 deletions pages/api/popover.md
Expand Up @@ -13,8 +13,10 @@ filename: /src/Popover/Popover.js
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| PaperProps | Object | | Properties applied to the `Paper` element. |
| anchorEl | HTMLElement | | This is the DOM element that will be used to set the position of the popover. |
| anchorOrigin | signature | { vertical: 'top', horizontal: 'left',} | This is the point on the anchor where the popover's `anchorEl` will attach to.<br>Options: vertical: [top, center, bottom]; horizontal: [left, center, right]. |
| anchorEl | HTMLElement | | This is the DOM element that may be used to set the position of the popover. |
| anchorOrigin | signature | { vertical: 'top', horizontal: 'left',} | This is the point on the anchor where the popover's `anchorEl` will attach to. This is not used when the anchorReference is 'anchorPosition'.<br>Options: vertical: [top, center, bottom]; horizontal: [left, center, right]. |
| anchorPosition | signature | { top: 0, left: 0,} | This is the position that may be used to set the position of the popover. The coordinates are relative to the application's client area. |
| anchorReference | union:&nbsp;'anchorEl'&nbsp;&#124;<br>&nbsp;'anchorPosition'<br> | 'anchorEl' | |
| <span style="color: #31a148">children *</span> | Node | | The content of the component. |
| classes | Object | | Useful to extend the style applied to components. |
| elevation | number | 8 | The elevation of the popover. |
Expand Down
37 changes: 32 additions & 5 deletions src/Popover/Popover.js
Expand Up @@ -83,6 +83,11 @@ export type Origin = {
vertical: 'top' | 'center' | 'bottom' | number,
};

export type Position = {
top: number,
left: number,
};

type ProvidedProps = {
anchorOrigin: Origin,
classes: Object,
Expand All @@ -92,13 +97,26 @@ type ProvidedProps = {

export type Props = {
/**
* This is the DOM element that will be used
* This is the DOM element that may be used
* to set the position of the popover.
*/
anchorEl?: ?HTMLElement,
/**
* This is the position that may be used
* to set the position of the popover.
* The coordinates are relative to
* the application's client area.
*/
anchorPosition?: Position,
/*
* This determines which anchor prop to refer to to set
* the position of the popover.
*/
anchorReference?: 'anchorEl' | 'anchorPosition',
/**
* This is the point on the anchor where the popover's
* `anchorEl` will attach to.
* `anchorEl` will attach to. This is not used when the
* anchorReference is 'anchorPosition'.
*
* Options:
* vertical: [top, center, bottom];
Expand Down Expand Up @@ -194,6 +212,7 @@ export type Props = {

class Popover extends React.Component<ProvidedProps & Props> {
static defaultProps = {
anchorReference: 'anchorEl',
anchorOrigin: {
vertical: 'top',
horizontal: 'left',
Expand Down Expand Up @@ -288,7 +307,12 @@ class Popover extends React.Component<ProvidedProps & Props> {
// to attach to on the anchor element (or body if none is provided)
getAnchorOffset(contentAnchorOffset) {
// $FlowFixMe
const { anchorEl, anchorOrigin } = this.props;
const { anchorEl, anchorOrigin, anchorReference, anchorPosition } = this.props;

if (anchorReference === 'anchorPosition') {
return anchorPosition;
}

const anchorElement = anchorEl || document.body;
const anchorRect = anchorElement.getBoundingClientRect();
const anchorVertical = contentAnchorOffset === 0 ? anchorOrigin.vertical : 'center';
Expand All @@ -301,10 +325,11 @@ class Popover extends React.Component<ProvidedProps & Props> {

// Returns the vertical offset of inner content to anchor the transform on if provided
getContentAnchorOffset(element) {
const { getContentAnchorEl, anchorReference } = this.props;
let contentAnchorOffset = 0;

if (this.props.getContentAnchorEl) {
const contentAnchorEl = this.props.getContentAnchorEl(element);
if (getContentAnchorEl && anchorReference === 'anchorEl') {
const contentAnchorEl = getContentAnchorEl(element);

if (contentAnchorEl && contains(element, contentAnchorEl)) {
const scrollTop = getScrollParent(element, contentAnchorEl);
Expand Down Expand Up @@ -359,6 +384,8 @@ class Popover extends React.Component<ProvidedProps & Props> {
render() {
const {
anchorEl,
anchorReference,
anchorPosition,
anchorOrigin,
children,
classes,
Expand Down
59 changes: 59 additions & 0 deletions src/Popover/Popover.spec.js
Expand Up @@ -415,6 +415,65 @@ describe('<Popover />', () => {
});
});

describe('positioning on a manual position', () => {
const anchorPosition = { top: 300, left: 500 };

let wrapper;
let popoverEl;
let openPopover;
let expectPopover;

before(() => {
openPopover = anchorOrigin => {
return new Promise(resolve => {
wrapper = mount(
<Popover
{...props}
anchorReference={'anchorPosition'}
anchorPosition={anchorPosition}
anchorOrigin={anchorOrigin}
transitionDuration={0}
onEntered={() => {
popoverEl = window.document.querySelector('[data-mui-test="Popover"]');
resolve();
}}
>
<div />
</Popover>,
);
wrapper.setProps({ open: true });
});
};

expectPopover = (top, left) => {
assert.strictEqual(
popoverEl.style.top,
`${top}px`,
'should position at the correct top offset',
);

assert.strictEqual(
popoverEl.style.left,
`${left}px`,
'should position at the correct left offset',
);
wrapper.unmount();
};
});

it('should be positioned according to the passed coordinates', () => {
return openPopover().then(() => {
expectPopover(anchorPosition.top, anchorPosition.left);
});
});

it('should ignore the anchorOrigin prop when being positioned', () => {
return openPopover({ vertical: 'top', horizontal: 'right' }).then(() => {
expectPopover(anchorPosition.top, anchorPosition.left);
});
});
});

describe('on window resize', () => {
let clock;

Expand Down