Skip to content

Commit

Permalink
Popover: refactor to TypeScript (#43823)
Browse files Browse the repository at this point in the history
* Rename files

* First iteration of Popover props

* Add types to component and default export

* Type AnimatedWrapper, refactor logic to always render a `motion.div`

* Remove unnused props from `ArrowTriangle`

* Use intermediate variable for flip/resize fallback values when `__unstableForcePosition` is passed

* Type PopoverSlot

* Type resize middleware

* Type onDialogClose & related

* Type middleware array

* Split and export different anchor ref types

* Type ownerDocument ev listeners useEffect

* Type internal state and ref callbacks

* Type mergedFloatingRef

* Fix type error in AnimatedWrapper s style

* Type arrow styles

* Simplify position pro definition

* Type placement-based utils

* Type getFrameOffset

* Type getReferenceOwnerDocument

* Make top and bottom props on anchorRef mandatory

* Type getReferenceElement

* anchorRef.startContainer is never undefined

* No need to optional-chain anchorRef.current.ownerDocument

* Remove TODO

* Fix getFrameOffset signature

* Simplify anchorRef types

* Add type descriptions

* Type `useDialog` hook

* Add @deprecated tag for the range prop

* Type Storybook examples

* refactor BorderControl and BorderBoxControl

* Add JSDoc description and example snippet to exported component

* Add `placement` s default value

* README

* CHANGELOGs

* Refine `position` types

* Type the `shift` prop, mark `__unstableShift` as deprecated

* focusOnMount typo

* Fix onFocusOutside README type

* fix getAnchorRect README type

* Fix anchorRef README type

* Fix children README type

* Fix isAlternate README type

* Remove unstable props from README, add deprecated range prop

* Improve focusOnMount types on derived components

* Do not display controls for `children` prop

* Storybook feedback

* Restore they way popoverProps was passed in BorderControl
  • Loading branch information
ciampo committed Sep 6, 2022
1 parent fac2865 commit a654883
Show file tree
Hide file tree
Showing 16 changed files with 582 additions and 311 deletions.
2 changes: 2 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
- `NavigatorButton`: updated to satisfy `react/exhaustive-deps` eslint rule ([#42051](https://github.com/WordPress/gutenberg/pull/42051))
- `TabPanel`: Refactor away from `_.partial()` ([#43895](https://github.com/WordPress/gutenberg/pull/43895/)).
- `Panel`: Refactor tests to `@testing-library/react` ([#43896](https://github.com/WordPress/gutenberg/pull/43896)).
- `Popover`: refactor to TypeScript ([#43823](https://github.com/WordPress/gutenberg/pull/43823/)).
- `BorderControl` and `BorderBoxControl`: replace temporary types with `Popover`'s types ([#43823](https://github.com/WordPress/gutenberg/pull/43823/)).

## 20.0.0 (2022-08-24)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* External dependencies
*/
import type { ComponentProps } from 'react';
/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -38,7 +42,9 @@ const BorderBoxControlSplitControls = (
} = useBorderBoxControlSplitControls( props );
const containerRef = useRef();
const mergedRef = useMergeRefs( [ containerRef, forwardedRef ] );
const popoverProps = popoverPlacement
const popoverProps: ComponentProps<
typeof BorderControl
>[ '__unstablePopoverProps' ] = popoverPlacement
? {
placement: popoverPlacement,
offset: popoverOffset,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import type { ComponentProps } from 'react';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -64,7 +69,9 @@ const BorderBoxControl = (
} = useBorderBoxControl( props );
const containerRef = useRef();
const mergedRef = useMergeRefs( [ containerRef, forwardedRef ] );
const popoverProps = popoverPlacement
const popoverProps: ComponentProps<
typeof BorderControl
>[ '__unstablePopoverProps' ] = popoverPlacement
? {
placement: popoverPlacement,
offset: popoverOffset,
Expand Down
9 changes: 5 additions & 4 deletions packages/components/src/border-box-control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Internal dependencies
*/
import type { Border, ColorProps, LabelProps } from '../border-control/types';
import type { PopoverProps } from '../popover/types';

export type Borders = {
top?: Border;
Expand Down Expand Up @@ -29,11 +30,11 @@ export type BorderBoxControlProps = ColorProps &
/**
* The position of the color popovers compared to the control wrapper.
*/
popoverPlacement?: string;
popoverPlacement?: PopoverProps[ 'placement' ];
/**
* The space between the popover and the control wrapper.
*/
popoverOffset?: number;
popoverOffset?: PopoverProps[ 'offset' ];
/**
* An object representing the current border configuration.
*
Expand Down Expand Up @@ -103,11 +104,11 @@ export type SplitControlsProps = ColorProps & {
/**
* The position of the color popovers compared to the control wrapper.
*/
popoverPlacement?: string;
popoverPlacement?: PopoverProps[ 'placement' ];
/**
* The space between the popover and the control wrapper.
*/
popoverOffset?: number;
popoverOffset?: PopoverProps[ 'offset' ];
/**
* An object representing the current border configuration. It contains
* properties for each side, with each side an object reflecting the border
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,7 @@ import { useBorderControlDropdown } from './hook';
import { StyledLabel } from '../../base-control/styles/base-control-styles';
import DropdownContentWrapper from '../../dropdown/dropdown-content-wrapper';

import type {
Color,
ColorOrigin,
Colors,
DropdownProps,
PopoverProps,
} from '../types';
import type { Color, ColorOrigin, Colors, DropdownProps } from '../types';

const noop = () => undefined;
const getColorObject = (
Expand Down Expand Up @@ -188,7 +182,8 @@ const BorderControlDropdown = (
</Button>
);

const renderContent = ( { onClose }: PopoverProps ) => (
// TODO: update types once Dropdown component is refactored to TypeScript.
const renderContent = ( { onClose }: { onClose: () => void } ) => (
<>
<DropdownContentWrapper paddingSize="medium">
<VStack className={ popoverControlsClassName } spacing={ 6 }>
Expand Down
16 changes: 7 additions & 9 deletions packages/components/src/border-control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
import type { CSSProperties } from 'react';

/**
* Internal dependencies
*/
import type { PopoverProps } from '../popover/types';

export type Border = {
color?: CSSProperties[ 'borderColor' ];
style?: CSSProperties[ 'borderStyle' ];
Expand Down Expand Up @@ -83,7 +88,7 @@ export type BorderControlProps = ColorProps &
/**
* An internal prop used to control the visibility of the dropdown.
*/
__unstablePopoverProps?: Record< string, unknown >;
__unstablePopoverProps?: Omit< PopoverProps, 'children' >;
/**
* If opted into, sanitizing the border means that if no width or color
* have been selected, the border style is also cleared and `undefined`
Expand Down Expand Up @@ -132,7 +137,7 @@ export type DropdownProps = ColorProps & {
/**
* An internal prop used to control the visibility of the dropdown.
*/
__unstablePopoverProps?: Record< string, unknown >;
__unstablePopoverProps?: Omit< PopoverProps, 'children' >;
/**
* This controls whether to render border style options.
*
Expand Down Expand Up @@ -176,10 +181,3 @@ export type StylePickerProps = LabelProps & {
*/
value?: string;
};

export type PopoverProps = {
/**
* Callback function to invoke when closing the border dropdown's popover.
*/
onClose: () => void;
};
10 changes: 6 additions & 4 deletions packages/components/src/dropdown/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dropdown

Dropdown is a React component to render a button that opens a floating content modal when clicked.
Dropdown is a React component to render a button that opens a floating content modal when clicked.

This component takes care of updating the state of the dropdown menu (opened/closed), handles closing the menu when clicking outside and uses render props to render the button and the content.

Expand Down Expand Up @@ -91,11 +91,13 @@ Set this to customize the text that is shown in the dropdown's header when it is

### focusOnMount

By default, the _first tabbable element_ in the popover will receive focus when it mounts. This is the same as setting `focusOnMount` to `"firstElement"`. If you want to focus the container instead, you can set `focusOnMount` to `"container"`.
By default, the _first tabbable element_ in the popover will receive focus when it mounts. This is the same as setting this prop to `"firstElement"`.

Set this prop to `false` to disable focus switching entirely. This should only be set when an appropriately accessible substitute behavior exists.
Specifying a `true` value will focus the container instead.

- Type: `String` or `Boolean`
Specifying a `false` value disables the focus handling entirely (this should only be done when an appropriately accessible substitute behavior exists).

- Type: `'firstElement' | boolean`
- Required: No
- Default: `"firstElement"`

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/dropdown/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default {
focusOnMount: {
control: {
type: 'radio',
options: [ 'firstElement', 'container', false ],
options: [ 'firstElement', true, false ],
},
},
headerTitle: { control: { type: 'text' } },
Expand Down
133 changes: 70 additions & 63 deletions packages/components/src/popover/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Popover

Popover is a React 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. It anchors itself to its parent node, optionally by a specified direction. If the popover exceeds the bounds of the page in the direction it opens, its position will be flipped automatically.
`Popover` renders its content in a floating modal. If no explicit anchor is passed via props, it anchors to its parent element by default.

The behavior of the popover when it exceeds the viewport's edges can be controlled via its props.

## Usage

Expand Down Expand Up @@ -49,130 +51,135 @@ render(

The component accepts the following props. Props not included in this set will be applied to the element wrapping Popover content.

### focusOnMount

By default, the _first tabblable element_ in the popover will receive focus when it mounts. This is the same as setting `focusOnMount` to `"firstElement"`. If you want to focus the container instead, you can set `focusOnMount` to `"container"`.
### `anchorRect`: `DomRectWithOwnerDocument`

Set this prop to `false` to disable focus changing entirely. This should only be set when an appropriately accessible substitute behavior exists.
An object extending a `DOMRect` with an additional optional `ownerDocument` property, used to specify a fixed popover position.

- Type: `String` or `Boolean`
- Required: No
- Default: `"firstElement"`

### placement
### `anchorRef`: `Element | PopoverAnchorRefReference | PopoverAnchorRefTopBottom | Range`

Used to specify a fixed popover position. It can be an `Element`, a React reference to an `element`, an object with a `top` and a `bottom` properties (both pointing to elements), or a `range`.

The direction in which the popover should open relative to its parent node or anchor node.
- Required: No

The available base placements are 'top', 'right', 'bottom', 'left'.
### `animate`: `boolean`

Each of these base placements has an alignment in the form -start and -end. For example, 'right-start', or 'bottom-end'. These allow you to align the tooltip to the edges of the button, rather than centering it.
Whether the popover should animate when opening.

- Type: `String`
- Required: No
- Default: `"bottom-start"`
- Default: `true`

### `children`: `ReactNode`

### flip
The `children` elements rendered as the popover's content.

Specifies whether the `Popover` should flip across its axis if there isn't space for it in the normal placement.
- Required: Yes

When the using a 'top' placement, the `Popover` will switch to a 'bottom' placement. When using a 'left' placement, the popover will switch to a 'right' placement.
### `expandOnMobile`: `boolean`

The `Popover` will retain its alignment of 'start' or 'end' when flipping.
Show the popover fullscreen on mobile viewports.

- Type: `Boolean`
- Required: No
- Default: `true`

### resize
### `flip`: `boolean`

Specifies whether the popover should flip across its axis if there isn't space for it in the normal placement.

When the using a 'top' placement, the popover will switch to a 'bottom' placement. When using a 'left' placement, the popover will switch to a `right' placement.

Adjusts the height of the `Popover` to prevent overflow.
The popover will retain its alignment of 'start' or 'end' when flipping.

- Type: `Boolean`
- Required: No
- Default: `true`

### shift
### `focusOnMount`: `'firstElement' | boolean`

Enables the `Popover` to shift in order to stay in view when meeting the viewport edges.
By default, the _first tabbable element_ in the popover will receive focus when it mounts. This is the same as setting this prop to `"firstElement"`.

Specifying a `true` value will focus the container instead.

Specifying a `false` value disables the focus handling entirely (this should only be done when an appropriately accessible substitute behavior exists).

- Type: `Boolean`
- Required: No
- Default: `false`
- Default: `"firstElement"`

### `onFocusOutside`: `( event: SyntheticEvent ) => void`

### offset
A callback invoked when the focus leaves the opened popover. This should only be provided in advanced use-cases when a popover should close under specific circumstances (for example, if the new `document.activeElement` is content of or otherwise controlling popover visibility).

The distance (in pixels) between the anchor and popover.
When not provided, the `onClose` callback will be called instead.

- Type: `Number`
- Required: No

### children
### `getAnchorRect`: `( fallbackReferenceElement: Element | null ) => DomRectWithOwnerDocument`

The content to be displayed within the popover.
A function returning the same value as the one expected by the `anchorRect` prop, used to specify a dynamic popover position.

- Type: `Element`
- Required: Yes
- Required: No

### className
### `headerTitle`: `string`

An optional additional class name to apply to the rendered popover.
Used to customize the header text shown when the popover is toggled to fullscreen on mobile viewports (see the `expandOnMobile` prop).

- Type: `String`
- Required: No

### onClose
### `isAlternate`: `boolean`

A callback invoked when the popover should be closed.
Used to enable a different visual style for the popover.

- Type: `Function`
- Required: No

### onFocusOutside

A callback invoked when the focus leaves the opened popover. This should only be provided in advanced use-cases when a Popover should close under specific circumstances; for example, if the new `document.activeElement` is content of or otherwise controlling Popover visibility.
### `noArrow`: `boolean`

Defaults to `onClose` when not provided.
Used to show/hide the arrow that points at the popover's anchor.

- Type: `Function`
- Required: No
- Default: `true`

### expandOnMobile
### `offset`: `number`

Opt-in prop to show popovers fullscreen on mobile, pass `false` in this prop to avoid this behavior.
The distance (in px) between the anchor and the popover.

- Type: `Boolean`
- Required: No
- Default: `false`

### headerTitle
### `onClose`: `() => void`

Set this to customize the text that is shown in popover's header when it is fullscreen on mobile.
A callback invoked when the popover should be closed.

- Type: `String`
- Required: No

### noArrow
### `placement`: `'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'`

Set this to hide the arrow which visually indicates what the popover is anchored to. Note that the arrow will not display if `position` is set to `"middle center"`.
Used to specify the popover's position with respect to its anchor.

- Type: `Boolean`
- Required: No
- Default: `true`
- Default: `"bottom-start"`

### anchorRect
### `position`: `[yAxis] [xAxis] [optionalCorner]`

_Note: use the `placement` prop instead when possible._

Legacy way to specify the popover's position with respect to its anchor.

Possible values:

- `yAxis`: `'top' | 'middle' | 'bottom'`
- `xAxis`: `'left' | 'center' | 'right'`
- `corner`: `'top' | 'right' | 'bottom' | 'left'`

A custom `DOMRect` object at which to position the popover. `anchorRect` is used when the position (custom `DOMRect` object) of the popover needs to be fixed at one location all the time.

- Type: `DOMRect`
- Required: No

### getAnchorRect
### `resize`: `boolean`

A callback function which is used to override the anchor value computation algorithm. `anchorRect` will take precedence over this prop, if both are passed together.
Adjusts the size of the popover to prevent its contents from going out of view when meeting the viewport edges.

- Required: No
- Default: `true`

If you need the `DOMRect` object i.e., the position of popover to be calculated on every time, the popover re-renders, then use `getAnchorRect`.
### `range`: `unknown`

`getAnchorRect` callback function receives a reference to the popover anchor element as a function parameter and it should return a `DOMRect` object. Noting that `getAnchorRect` can be called with `null`.
_Note: this prop is deprecated and has no effect on the component._

- Type: `Function`
- Required: No
Loading

0 comments on commit a654883

Please sign in to comment.