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

Components: promote g2 Popover as Flyout #32197

Merged
merged 25 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e2efe53
Move `components/src/ui/popover` to `components/src/accessible-popover`
ciampo May 25, 2021
650deeb
Add to Docs manifest
ciampo May 25, 2021
777f822
Add to tsconfig.json
ciampo May 25, 2021
8363483
Rename context-related variables from Popover* to AccessiblePopover*
ciampo Jun 25, 2021
8e0d236
Rename `PopoverContext` type to `Context`
ciampo Jun 25, 2021
59c21e4
Rename `PopoverContent` to `AccessiblePopoverContent`
ciampo Jun 25, 2021
935bc5e
Move `component.js` to component-specific folder
ciampo Jun 25, 2021
ed58977
Move `AccessibilePopover` logic to separate hook
ciampo Jun 25, 2021
083fe44
Move `content.js` to component-specific folder
ciampo Jun 25, 2021
5c15310
Move `AccessibilePopoverContent` logic to separate hook
ciampo Jun 25, 2021
1e68e3c
Rename `AccessiblePopover` to `Flyout`
ciampo Jun 25, 2021
5d3428c
Rename zIndex const from `Popover` to `Flyout`
ciampo Jun 30, 2021
2d92a1e
Do not export FlyoutContent component
ciampo Jul 1, 2021
294279d
Fix imports in example
ciampo Jul 1, 2021
337249a
Rename `popover` to `flyoutState`
ciampo Jul 1, 2021
cd5d33b
Refactor to `styled` approach
ciampo Jul 1, 2021
91857d9
Wrap `ReakitPopover` in `FlyoutContentView` and set `maxWidth` in inl…
ciampo Jul 1, 2021
8d31c25
auto-format
ciampo Jul 1, 2021
db1cbae
Add Props documentation to Flyout
ciampo Jul 1, 2021
00cc295
Mark `Flyout` as non-polymorphic
ciampo Jul 2, 2021
1b5f2a5
Add documentation to `FlyoutContent`s README
ciampo Jul 2, 2021
a7389ba
Update snapshots
ciampo Jul 2, 2021
23b945e
Remove FlyoutContext exports
ciampo Jul 2, 2021
edfb229
Do not export FlyoutContent`s documentation
ciampo Jul 2, 2021
2b5d016
Delete FlyoutContent`s README
ciampo Jul 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,12 @@
"markdown_source": "../packages/components/src/flex/flex/README.md",
"parent": "components"
},
{
"title": "Flyout",
"slug": "flyout",
"markdown_source": "../packages/components/src/flyout/flyout/README.md",
"parent": "components"
},
{
"title": "FocalPointPicker",
"slug": "focal-point-picker",
Expand Down
10 changes: 10 additions & 0 deletions packages/components/src/flyout/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* WordPress dependencies
*/
import { createContext, useContext } from '@wordpress/element';

/**
* @type {import('react').Context<import('./types').Context>}
*/
export const FlyoutContext = createContext( {} );
export const useFlyoutContext = () => useContext( FlyoutContext );
53 changes: 53 additions & 0 deletions packages/components/src/flyout/flyout-content/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Internal dependencies
*/
import { useFlyoutContext } from '../context';
import { FlyoutContentView, CardView } from '../styles';
import { contextConnect, useContextSystem } from '../../ui/context';

/**
*
* @param {import('../../ui/context').PolymorphicComponentProps<import('../types').ContentProps, 'div', false>} props
Copy link
Contributor Author

@ciampo ciampo Jul 1, 2021

Choose a reason for hiding this comment

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

The refactoring done in 3598b73, (where we wrapped ReakitPopover directly into the styled function), caused a typescript error around the as prop on < FlyoutContentView />.

For this reason, FlyoutContent (and Flyout) have been marked as non-polymorphic

Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, what's the context behind refactoring the css calls to styled.div and then to styled( ReakitPopover )? I remember we've had some conversations about that, but I can't remember exactly the reasoning behind it. cc @sarayourfriend

Copy link
Contributor

Choose a reason for hiding this comment

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

Basically @emotion/css does not hook into the Emotion cache so it's not possible for us to use it's css function because we need to hook into the cache to support iframes.

@emotion/styled does hook into the CacheProvider so it is able to support iframes.

Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah! That makes sense.

* @param {import('react').Ref<any>} forwardedRef
*/
function FlyoutContent( props, forwardedRef ) {
const {
children,
elevation,
maxWidth,
style = {},
...otherProps
} = useContextSystem( props, 'FlyoutContent' );

const { label, flyoutState } = useFlyoutContext();

if ( ! flyoutState ) {
throw new Error(
'`FlyoutContent` must only be used inside a `Flyout`.'
);
}

const showContent = flyoutState.visible || flyoutState.animating;

return (
<FlyoutContentView
aria-label={ label }
// maxWidth is applied via inline styles in order to avoid the `React does
// not recognize the maxWidth prop on a DOM element` error that comes from
// passing `maxWidth` as a prop to `FlyoutContentView`
style={ { maxWidth, ...style } }
{ ...otherProps }
{ ...flyoutState }
>
{ showContent && (
<CardView elevation={ elevation } ref={ forwardedRef }>
{ children }
</CardView>
) }
</FlyoutContentView>
);
}

const ConnectedFlyoutContent = contextConnect( FlyoutContent, 'FlyoutContent' );

export default ConnectedFlyoutContent;
1 change: 1 addition & 0 deletions packages/components/src/flyout/flyout-content/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './component';
98 changes: 98 additions & 0 deletions packages/components/src/flyout/flyout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Flyout

<div class="callout callout-alert">
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
</div>

`Flyout` is a component to render a floating content modal. It is similar in purpose to a tooltip, but renders content of any sort, not only simple text.

## Usage

```jsx
import { Button, __experimentalFlyout as Flyout, __experimentalText as } from '@wordpress/components';

function Example() {
return (
<Flyout trigger={ <Button>Show/Hide content</Button> }>
<Text>Code is Poetry</Text>
</Flyout>
);
}
```

## Props

### `state`: `PopoverStateReturn`

- Required: No

### `label`: `string`

- Required: No

### `animated`: `boolean`

Determines if `Flyout` has animations.

- Required: No
- Default: `true`

### `animationDuration`: `boolean`

The duration of `Flyout` animations.

- Required: No
- Default: `160`

### `baseId`: `string`

ID that will serve as a base for all the items IDs. See https://reakit.io/docs/popover/#usepopoverstate

- Required: No
- Default: `160`

### `elevation`: `number`

Size of the elevation shadow. For more information, check out [`Card`](/packages/components/src/card/card/README.md#props).

- Required: No
- Default: `5`

### `maxWidth`: `CSSProperties[ 'maxWidth' ]`

Max-width for the `Flyout` element.

- Required: No
- Default: `360`

### `onVisibleChange`: `( ...args: any ) => void`

Callback for when the `visible` state changes.

- Required: No

### `trigger`: `FunctionComponentElement< any >`

Element that triggers the `visible` state of `Flyout` when clicked.

```jsx
<Flyout trigger={<Button>Greet</Button>}>
<Text>Hi! I'm Olaf!</Text>
</Flyout>
```

- Required: Yes

### `visible`: `boolean`

Whether `Flyout` is visible. See [the `Reakit` docs](https://reakit.io/docs/popover/#usepopoverstate) for more information.

- Required: No
- Default: `false`

### `placement`: `PopperPlacement`

Position of the popover element. See [the `popper` docs](https://popper.js.org/docs/v1/#popperplacements--codeenumcode) for more information.

- Required: No
- Default: `auto`
111 changes: 111 additions & 0 deletions packages/components/src/flyout/flyout/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* External dependencies
*/
// eslint-disable-next-line no-restricted-imports
import { PopoverDisclosure, Portal } from 'reakit';

/**
* WordPress dependencies
*/
import { useCallback, useMemo, cloneElement } from '@wordpress/element';

/**
* Internal dependencies
*/
import { contextConnect } from '../../ui/context';
import { FlyoutContext } from '../context';
import { useFlyoutResizeUpdater } from '../utils';
import FlyoutContent from '../flyout-content';
import { useUpdateEffect } from '../../utils/hooks';
import { useFlyout } from './hook';

/**
*
* @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div', false>} props
* @param {import('react').Ref<any>} forwardedRef
*/
function Flyout( props, forwardedRef ) {
const {
children,
elevation,
label,
maxWidth,
onVisibleChange,
trigger,
flyoutState,
...otherProps
} = useFlyout( props );

const resizeListener = useFlyoutResizeUpdater( {
onResize: flyoutState.unstable_update,
} );

const uniqueId = `flyout-${ flyoutState.baseId }`;
const labelId = label || uniqueId;

const contextProps = useMemo(
() => ( {
label: labelId,
flyoutState,
} ),
[ labelId, flyoutState ]
);

const triggerContent = useCallback(
( triggerProps ) => {
return cloneElement( trigger, triggerProps );
},
[ trigger ]
);

useUpdateEffect( () => {
onVisibleChange?.( flyoutState.visible );
}, [ flyoutState.visible ] );

return (
<FlyoutContext.Provider value={ contextProps }>
{ trigger && (
<PopoverDisclosure
{ ...flyoutState }
ref={ trigger.ref }
{ ...trigger.props }
>
{ triggerContent }
</PopoverDisclosure>
) }
<Portal>
<FlyoutContent
ref={ forwardedRef }
{ ...otherProps }
elevation={ elevation }
maxWidth={ maxWidth }
>
{ resizeListener }
{ children }
</FlyoutContent>
</Portal>
</FlyoutContext.Provider>
);
}

/**
* `Flyout` is a component to render a floating content modal.
* It is similar in purpose to a tooltip, but renders content of any sort,
* not only simple text.
*
* @example
* ```jsx
* import { Button, __experimentalFlyout as Flyout, __experimentalText as } from '@wordpress/components';
*
* function Example() {
* return (
* <Flyout trigger={ <Button>Show/Hide content</Button> }>
* <Text>Code is Poetry</Text>
* </Flyout>
* );
* }
* ```
*/
const ConnectedFlyout = contextConnect( Flyout, 'Flyout' );

export default ConnectedFlyout;
45 changes: 45 additions & 0 deletions packages/components/src/flyout/flyout/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* External dependencies
*/
// eslint-disable-next-line no-restricted-imports
import { usePopoverState } from 'reakit';

/**
* Internal dependencies
*/
import { useContextSystem } from '../../ui/context';

/**
* @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div', false>} props
*/
export function useFlyout( props ) {
const {
animated = true,
animationDuration = 160,
baseId,
elevation = 5,
id,
maxWidth = 360,
placement,
state,
visible,
...otherProps
} = useContextSystem( props, 'Flyout' );

const _flyoutState = usePopoverState( {
animated: animated ? animationDuration : undefined,
baseId: baseId || id,
placement,
visible,
...otherProps,
} );

const flyoutState = state || _flyoutState;

return {
...otherProps,
elevation,
maxWidth,
flyoutState,
};
}
2 changes: 2 additions & 0 deletions packages/components/src/flyout/flyout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './component';
export { useFlyout } from './hook';
1 change: 1 addition & 0 deletions packages/components/src/flyout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Flyout } from './flyout';
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
/**
* Internal dependencies
*/
import { CardBody, CardHeader } from '../../../card';
import Button from '../../../button';
import { Popover } from '..';
import { CardBody, CardHeader } from '../../card';
import Button from '../../button';
import { Flyout } from '..';

export default {
component: Popover,
title: 'G2 Components (Experimental)/Popover',
component: Flyout,
title: 'Components (Experimental)/Flyout',
};

export const _default = () => {
return (
<Popover
<Flyout
trigger={ <Button>Click</Button> }
visible
placement="bottom-start"
>
<CardHeader>Go</CardHeader>
<CardBody>Stuff</CardBody>
</Popover>
</Flyout>
);
};
Loading