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

PanelBody: Convert to TypeScript #47702

Merged
merged 18 commits into from
Mar 2, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
### Internal

- `Guide`: Convert to TypeScript ([#47493](https://github.com/WordPress/gutenberg/pull/47493)).
- `PanelBody`: Convert to TypeScript ([#47702](https://github.com/WordPress/gutenberg/pull/47702)).

## 23.5.0 (2023-03-01)

### Enhancements

- `ToolsPanel`: Separate reset all filter registration from items registration and support global resets ([#48123](https://github.com/WordPress/gutenberg/pull/48123#pullrequestreview-1308386926)).
- `ToolsPanel`: Separate reset all filter registration from items registration and support global resets ([#48123](https://github.com/WordPress/gutenberg/pull/48123#pullrequestreview-1308386926)).

### Internal

Expand All @@ -31,10 +32,10 @@

### Enhancements

- `ColorPalette`, `GradientPicker`, `PaletteEdit`, `ToolsPanel`: add new props to set a custom heading level ([43848](https://github.com/WordPress/gutenberg/pull/43848) and [#47788](https://github.com/WordPress/gutenberg/pull/47788)).
- `ColorPalette`, `GradientPicker`, `PaletteEdit`, `ToolsPanel`: add new props to set a custom heading level ([43848](https://github.com/WordPress/gutenberg/pull/43848) and [#47788](https://github.com/WordPress/gutenberg/pull/47788)).
ciampo marked this conversation as resolved.
Show resolved Hide resolved
- `ColorPalette`: ensure text label contrast checking works with CSS variables ([#47373](https://github.com/WordPress/gutenberg/pull/47373)).
- `Navigator`: Support dynamic paths with parameters ([#47827](https://github.com/WordPress/gutenberg/pull/47827)).
- `Navigator`: Support hierarchical paths navigation and add `NavigatorToParentButton` component ([#47883](https://github.com/WordPress/gutenberg/pull/47883)).
- `Navigator`: Support dynamic paths with parameters ([#47827](https://github.com/WordPress/gutenberg/pull/47827)).
- `Navigator`: Support hierarchical paths navigation and add `NavigatorToParentButton` component ([#47883](https://github.com/WordPress/gutenberg/pull/47883)).

### Internal

Expand Down Expand Up @@ -63,7 +64,7 @@

### Enhancements

- `Dropdown`: deprecate `position` prop, use `popoverProps` instead ([46865](https://github.com/WordPress/gutenberg/pull/46865)).
- `Dropdown`: deprecate `position` prop, use `popoverProps` instead ([46865](https://github.com/WordPress/gutenberg/pull/46865)).
- `Button`: improve padding for buttons with icon and text. ([46764](https://github.com/WordPress/gutenberg/pull/46764)).
- `ColorPalette`: Use computed color when css variable is passed to `ColorPicker` ([47181](https://github.com/WordPress/gutenberg/pull/47181)).
- `Popover`: add `overlay` option to the `placement` prop ([47004](https://github.com/WordPress/gutenberg/pull/47004)).
Expand All @@ -75,8 +76,7 @@
- Removed deprecated `@storybook/addon-knobs` dependency from the package ([47152](https://github.com/WordPress/gutenberg/pull/47152)).
- `ColorListPicker`: Convert to TypeScript ([#46358](https://github.com/WordPress/gutenberg/pull/46358)).
- `KeyboardShortcuts`: Convert to TypeScript ([#47429](https://github.com/WordPress/gutenberg/pull/47429)).
- `ColorPalette`, `BorderControl`, `GradientPicker`: refine types and logic around single vs multiple palettes
([#47384](https://github.com/WordPress/gutenberg/pull/47384)).
- `ColorPalette`, `BorderControl`, `GradientPicker`: refine types and logic around single vs multiple palettes ([#47384](https://github.com/WordPress/gutenberg/pull/47384)).
- `Button`: Convert to TypeScript ([#46997](https://github.com/WordPress/gutenberg/pull/46997)).
- `QueryControls`: Convert to TypeScript ([#46721](https://github.com/WordPress/gutenberg/pull/46721)).
- `TreeGrid`: Convert to TypeScript ([#47516](https://github.com/WordPress/gutenberg/pull/47516)).
Expand All @@ -91,6 +91,7 @@
## 23.2.0 (2023-01-11)

### Internal

- `AlignmentMatrixControl`: Update center cell label to 'Center' instead of 'Center Center' ([#46852](https://github.com/WordPress/gutenberg/pull/46852)).
- `Toolbar`: move all subcomponents under the same folder ([46951](https://github.com/WordPress/gutenberg/pull/46951)).
- `Dashicon`: remove unnecessary type for `className` prop ([46849](https://github.com/WordPress/gutenberg/pull/46849)).
Expand Down
88 changes: 53 additions & 35 deletions packages/components/src/panel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,25 @@ const MyPanel = () => (

##### Props

###### className
###### `header`: `string`

The class that will be added with `components-panel`. If no `className` is passed only `components-panel__body` and `is-opened` is used.
The text that will be rendered as the title of the panel. Text will be rendered inside an
`<h2>` tag.

- Type: `String`
- Required: No

###### header
###### `className`: `string`

Title of the `Panel`. Text will be rendered inside an `<h2>` tag.
The CSS class to apply to the wrapper element.

- Type: `String`
- Required: No

###### `children`: `React.ReactNode`

The content to display within the panel row.

- Required: Yes

---

#### PanelBody
Expand All @@ -99,64 +104,67 @@ The `PanelBody` creates a collapsible container that can be toggled open or clos

##### Props

###### title
###### `title`: `string`

Title of the `PanelBody`. This shows even when it is closed.
Title text. It shows even when the component is closed.

- Type: `String`
- Required: No

###### opened
###### `opened`: `boolean`

If opened is true then the `Panel` will remain open regardless of the `initialOpen` prop and the panel will be prevented from being closed.
When set to `true`, the component will remain open regardless of the `initialOpen` prop and the
panel will be prevented from being closed.

- Type: `Boolean`
- Required: No

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

The class that will be added with `components-panel__body`, if the panel is currently open, the `is-opened` class will also be passed to the classes of the wrapper div. If no `className` is passed then only `components-panel__body` and `is-opened` is used.
The CSS class to apply to the wrapper element.

- Type: `String`
- Required: No

###### icon
###### `icon`: `JSX.Element`

An icon to be shown next to the `PanelBody` title.
An icon to be shown next to the title.

- Type: `String`
- Required: No

###### onToggle
###### `onToggle`: `( next: boolean ) => void;`

A function that is called when the user clicks on the `PanelBody` title after the open state is changed.
A function that is called any time the component is toggled from its closed state to its
opened state, or vice versa.

- Type: `function`
- Required: No
- Default: `noop`

###### initialOpen
###### `initialOpen`: `boolean`

Whether or not the panel will start open.

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

###### children
###### `children`: `| React.ReactNode | ( ( props: { opened: boolean } ) => React.ReactNode )`

The rendered children. If the children is a `Function`, it will be called with an object with the `opened` property and return its value.
The content to display in the `PanelBody`. If a function is provided for this prop, it will receive an object with the `opened` prop as an argument.

- Type: `React.ReactNode | Function`
- Required: No

###### buttonProps
###### `buttonProps`: `WordPressComponentProps<Omit< ButtonAsButtonProps, 'icon' >, 'button', false>`

Props that are passed to the `Button` component in the `PanelBodyTitle` within the panel body.
Props that are passed to the `Button` component in title within the `PanelBody`.

- Type: `Object`
- Required: No
- Default: `{}`

###### `scrollAfterOpen`: `boolean`

Scrolls the content into view when visible. This improves the UX when multiple `PanelBody`
components are stacked in a scrollable container.

- Required: No
- Default: `true`

---

#### PanelRow
Expand All @@ -165,11 +173,16 @@ Props that are passed to the `Button` component in the `PanelBodyTitle` within t

##### Props

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

The class that will be added with `components-panel__row`. to the classes of the wrapper div. If no `className` is passed only `components-panel__row` is used.
The CSS class to apply to the wrapper element.

- Required: No

###### `children`: `React.ReactNode`

The content to display within the panel row.

- Type: `String`
- Required: No

##### Ref
Expand All @@ -186,11 +199,16 @@ PanelRow accepts a forwarded ref that will be added to the wrapper div. Usage:

##### Props

###### label
###### `label`: `string`

The text that will be rendered as the title of the `Panel`. Will be rendered in an `<h2>` tag.

- Type: `String`
- Required: No

###### `children`: `React.ReactNode`

The content to display within the panel row.

- Required: No

## Related components
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ import { chevronUp, chevronDown } from '@wordpress/icons';
/**
* Internal dependencies
*/
import type { PanelBodyProps, PanelBodyTitleProps } from './types';
import type { WordPressComponentProps } from '../ui/context';
import Button from '../button';
import Icon from '../icon';
import { useControlledState, useUpdateEffect } from '../utils';

const noop = () => {};

export function PanelBody(
{
export function UnforwardedPanelBody(
props: PanelBodyProps,
ref: React.ForwardedRef< HTMLDivElement >
) {
const {
buttonProps = {},
children,
className,
Expand All @@ -30,27 +35,29 @@ export function PanelBody(
opened,
title,
scrollAfterOpen = true,
},
ref
) {
const [ isOpened, setIsOpened ] = useControlledState( opened, {
initial: initialOpen === undefined ? true : initialOpen,
} );
const nodeRef = useRef();
} = props;
const [ isOpened, setIsOpened ] = useControlledState< boolean | undefined >(
opened,
{
initial: initialOpen === undefined ? true : initialOpen,
fallback: false,
}
);
ciampo marked this conversation as resolved.
Show resolved Hide resolved
const nodeRef = useRef< HTMLElement >( null );

// Defaults to 'smooth' scrolling
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
const scrollBehavior = useReducedMotion() ? 'auto' : 'smooth';

const handleOnToggle = ( event ) => {
const handleOnToggle = ( event: React.MouseEvent ) => {
event.preventDefault();
const next = ! isOpened;
setIsOpened( next );
onToggle( next );
};

// Ref is used so that the effect does not re-run upon scrollAfterOpen changing value.
const scrollAfterOpenRef = useRef();
const scrollAfterOpenRef = useRef< boolean | undefined >();
scrollAfterOpenRef.current = scrollAfterOpen;
// Runs after initial render.
useUpdateEffect( () => {
Expand Down Expand Up @@ -80,20 +87,28 @@ export function PanelBody(
<div className={ classes } ref={ useMergeRefs( [ nodeRef, ref ] ) }>
<PanelBodyTitle
icon={ icon }
isOpened={ isOpened }
isOpened={ Boolean( isOpened ) }
onClick={ handleOnToggle }
title={ title }
{ ...buttonProps }
/>
{ typeof children === 'function'
? children( { opened: isOpened } )
? children( { opened: Boolean( isOpened ) } )
: isOpened && children }
</div>
);
}

const PanelBodyTitle = forwardRef(
( { isOpened, icon, title, ...props }, ref ) => {
ciampo marked this conversation as resolved.
Show resolved Hide resolved
(
{
isOpened,
icon,
title,
...props
}: WordPressComponentProps< PanelBodyTitleProps, 'button' >,
ref: React.ForwardedRef< any >
) => {
if ( ! title ) return null;

return (
Expand Down Expand Up @@ -128,7 +143,6 @@ const PanelBodyTitle = forwardRef(
}
);

const ForwardedComponent = forwardRef( PanelBody );
ForwardedComponent.displayName = 'PanelBody';
export const PanelBody = forwardRef( UnforwardedPanelBody );

export default ForwardedComponent;
export default PanelBody;
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* Internal dependencies
*/
Expand All @@ -11,7 +16,7 @@ import InputControl from '../../input-control';
*/
import { wordpress } from '@wordpress/icons';

export default {
const meta: ComponentMeta< typeof Panel > = {
title: 'Components/Panel',
component: Panel,
subcomponents: { PanelRow, PanelBody },
Expand All @@ -23,10 +28,13 @@ export default {
docs: { source: { state: 'open' } },
},
};
export default meta;

const Template = ( props ) => <Panel { ...props } />;
const Template: ComponentStory< typeof Panel > = ( props ) => (
<Panel { ...props } />
);

export const Default = Template.bind( {} );
export const Default: ComponentStory< typeof Panel > = Template.bind( {} );
Default.args = {
header: 'My panel',
children: (
Expand Down Expand Up @@ -61,7 +69,7 @@ Default.args = {
* `PanelRow` is a generic container for rows within a `PanelBody`.
* It is a flex container with a top margin for spacing.
*/
export const _PanelRow = Template.bind( {} );
export const _PanelRow: ComponentStory< typeof Panel > = Template.bind( {} );
_PanelRow.args = {
children: (
<PanelBody title="My Profile">
Expand All @@ -78,7 +86,9 @@ _PanelRow.args = {
),
};

export const DisabledSection = Template.bind( {} );
export const DisabledSection: ComponentStory< typeof Panel > = Template.bind(
{}
);
DisabledSection.args = {
...Default.args,
children: (
Expand All @@ -90,7 +100,7 @@ DisabledSection.args = {
),
};

export const WithIcon = Template.bind( {} );
export const WithIcon: ComponentStory< typeof Panel > = Template.bind( {} );
WithIcon.args = {
...Default.args,
children: (
Expand Down
Loading