diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index de726e6106e9b..249ae1cfa617a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -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 @@ -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)). - `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 @@ -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)). @@ -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)). @@ -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)). diff --git a/packages/components/src/panel/README.md b/packages/components/src/panel/README.md index 04cf6d6eeeda1..be4beca3e7527 100644 --- a/packages/components/src/panel/README.md +++ b/packages/components/src/panel/README.md @@ -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 +`

` tag. -- Type: `String` - Required: No -###### header +###### `className`: `string` -Title of the `Panel`. Text will be rendered inside an `

` 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 @@ -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, '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 @@ -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 @@ -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 `

` tag. -- Type: `String` +- Required: No + +###### `children`: `React.ReactNode` + +The content to display within the panel row. + - Required: No ## Related components diff --git a/packages/components/src/panel/body.js b/packages/components/src/panel/body.tsx similarity index 73% rename from packages/components/src/panel/body.js rename to packages/components/src/panel/body.tsx index f47d0be42ad00..f5ea844754cdd 100644 --- a/packages/components/src/panel/body.js +++ b/packages/components/src/panel/body.tsx @@ -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, @@ -30,19 +35,21 @@ 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, + } + ); + 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 ); @@ -50,7 +57,7 @@ export function PanelBody( }; // 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( () => { @@ -80,20 +87,28 @@ export function PanelBody(
{ typeof children === 'function' - ? children( { opened: isOpened } ) + ? children( { opened: Boolean( isOpened ) } ) : isOpened && children }
); } const PanelBodyTitle = forwardRef( - ( { isOpened, icon, title, ...props }, ref ) => { + ( + { + isOpened, + icon, + title, + ...props + }: WordPressComponentProps< PanelBodyTitleProps, 'button' >, + ref: React.ForwardedRef< any > + ) => { if ( ! title ) return null; return ( @@ -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; diff --git a/packages/components/src/panel/stories/index.js b/packages/components/src/panel/stories/index.tsx similarity index 76% rename from packages/components/src/panel/stories/index.js rename to packages/components/src/panel/stories/index.tsx index 29dab9b293bed..b3c189d11cb88 100644 --- a/packages/components/src/panel/stories/index.js +++ b/packages/components/src/panel/stories/index.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + /** * Internal dependencies */ @@ -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 }, @@ -23,10 +28,13 @@ export default { docs: { source: { state: 'open' } }, }, }; +export default meta; -const Template = ( props ) => ; +const Template: ComponentStory< typeof Panel > = ( props ) => ( + +); -export const Default = Template.bind( {} ); +export const Default: ComponentStory< typeof Panel > = Template.bind( {} ); Default.args = { header: 'My panel', children: ( @@ -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: ( @@ -78,7 +86,9 @@ _PanelRow.args = { ), }; -export const DisabledSection = Template.bind( {} ); +export const DisabledSection: ComponentStory< typeof Panel > = Template.bind( + {} +); DisabledSection.args = { ...Default.args, children: ( @@ -90,7 +100,7 @@ DisabledSection.args = { ), }; -export const WithIcon = Template.bind( {} ); +export const WithIcon: ComponentStory< typeof Panel > = Template.bind( {} ); WithIcon.args = { ...Default.args, children: ( diff --git a/packages/components/src/panel/test/__snapshots__/body.js.snap b/packages/components/src/panel/test/__snapshots__/body.tsx.snap similarity index 100% rename from packages/components/src/panel/test/__snapshots__/body.js.snap rename to packages/components/src/panel/test/__snapshots__/body.tsx.snap diff --git a/packages/components/src/panel/test/body.js b/packages/components/src/panel/test/body.tsx similarity index 83% rename from packages/components/src/panel/test/body.js rename to packages/components/src/panel/test/body.tsx index 1f282256fb261..fb745f2c602db 100644 --- a/packages/components/src/panel/test/body.js +++ b/packages/components/src/panel/test/body.tsx @@ -89,9 +89,7 @@ describe( 'PanelBody', () => { ); - let panelContent = screen.getByTestId( 'inner-content' ); - - expect( panelContent ).toBeVisible(); + expect( screen.getByTestId( 'inner-content' ) ).toBeVisible(); rerender( @@ -99,9 +97,9 @@ describe( 'PanelBody', () => { ); - panelContent = screen.queryByTestId( 'inner-content' ); - - expect( panelContent ).not.toBeInTheDocument(); + expect( + screen.queryByTestId( 'inner-content' ) + ).not.toBeInTheDocument(); rerender( @@ -109,9 +107,7 @@ describe( 'PanelBody', () => { ); - panelContent = screen.getByTestId( 'inner-content' ); - - expect( panelContent ).toBeVisible(); + expect( screen.getByTestId( 'inner-content' ) ).toBeVisible(); } ); it( 'should toggle when clicking header', async () => { @@ -123,22 +119,21 @@ describe( 'PanelBody', () => { ); - let panelContent = screen.queryByTestId( 'inner-content' ); const panelToggle = screen.getByRole( 'button', { name: 'Panel' } ); - expect( panelContent ).not.toBeInTheDocument(); + expect( + screen.queryByTestId( 'inner-content' ) + ).not.toBeInTheDocument(); await user.click( panelToggle ); - panelContent = screen.getByTestId( 'inner-content' ); - - expect( panelContent ).toBeVisible(); + expect( screen.getByTestId( 'inner-content' ) ).toBeVisible(); await user.click( panelToggle ); - panelContent = screen.queryByTestId( 'inner-content' ); - - expect( panelContent ).not.toBeInTheDocument(); + expect( + screen.queryByTestId( 'inner-content' ) + ).not.toBeInTheDocument(); } ); it( 'should pass button props to panel title', async () => { diff --git a/packages/components/src/panel/types.ts b/packages/components/src/panel/types.ts index fe41ad378d62b..9059aad53fa0a 100644 --- a/packages/components/src/panel/types.ts +++ b/packages/components/src/panel/types.ts @@ -1,3 +1,9 @@ +/** + * Internal dependencies + */ +import type { ButtonAsButtonProps } from '../button/types'; +import type { WordPressComponentProps } from '../ui/context'; + export type PanelProps = { /** * The text that will be rendered as the title of the panel. @@ -36,3 +42,75 @@ export type PanelRowProps = { */ children: React.ReactNode; }; + +export type PanelBodyProps = { + /** + * Props that are passed to the `Button` component in title within the + * `PanelBody`. + * + * @default {} + */ + buttonProps?: WordPressComponentProps< + Omit< ButtonAsButtonProps, 'icon' >, + 'button', + false + >; + /** + * 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. + */ + children?: + | React.ReactNode + | ( ( props: { opened: boolean } ) => React.ReactNode ); + + /** + * The CSS class to apply to the wrapper element. + */ + className?: string; + /** + * An icon to be shown next to the title. + */ + icon?: JSX.Element; + /** + * Whether or not the panel will start open. + */ + initialOpen?: boolean; + /** + * A function that is called any time the component is toggled from its closed + * state to its opened state, or vice versa. + * + * @default noop + */ + onToggle?: ( next: boolean ) => void; + /** + * When set to `true`, the component will remain open regardless of the + * `initialOpen` prop and the panel will be prevented from being closed. + */ + opened?: boolean; + /** + * Title text. It shows even when it is closed. + */ + title?: string; + /** + * Scrolls the content into view when visible. This improves the UX when + * multiple `PanelBody` components are stacked in a scrollable container. + * + * @default true + */ + scrollAfterOpen?: boolean; +}; + +export type PanelBodyTitleProps = Omit< ButtonAsButtonProps, 'icon' > & { + /** + * An icon to be shown next to the title. + */ + icon?: JSX.Element; + /** + * Whether or not the `PanelBody` is currently opened or not. + */ + isOpened?: boolean; + /** + * The title text. + */ + title?: string; +}; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 005ddd0b4c568..b2ef337c4221e 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -55,7 +55,6 @@ "src/higher-order/with-focus-return", "src/higher-order/with-notices", "src/navigation", - "src/palette-edit", - "src/panel/body.js" + "src/palette-edit" ] }