From 78dde7f16d6c57d3a1da5a7a12264a528ffa2ed4 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 9 Feb 2023 20:07:06 +0100 Subject: [PATCH 01/21] Rename files --- .../components/src/tools-panel/stories/{index.js => index.tsx} | 0 ...th-item-group-slot.js => tools-panel-with-item-group-slot.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/components/src/tools-panel/stories/{index.js => index.tsx} (100%) rename packages/components/src/tools-panel/stories/utils/{tools-panel-with-item-group-slot.js => tools-panel-with-item-group-slot.tsx} (100%) diff --git a/packages/components/src/tools-panel/stories/index.js b/packages/components/src/tools-panel/stories/index.tsx similarity index 100% rename from packages/components/src/tools-panel/stories/index.js rename to packages/components/src/tools-panel/stories/index.tsx diff --git a/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx similarity index 100% rename from packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js rename to packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx From cb303b819f02da78358c70078086de96527e2256 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 9 Feb 2023 20:07:44 +0100 Subject: [PATCH 02/21] Fix imports --- .../components/src/tools-panel/stories/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index 0ff9824406a914..69a3988baf1530 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -7,15 +7,19 @@ import styled from '@emotion/styled'; * WordPress dependencies */ import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ import { - __experimentalToggleGroupControl as ToggleGroupControl, - __experimentalToggleGroupControlOption as ToggleGroupControlOption, -} from '@wordpress/components'; + ToggleGroupControl, + ToggleGroupControlOption, +} from '../../toggle-group-control'; /** * Internal dependencies */ -import { ToolsPanel, ToolsPanelItem } from '../'; +import { ToolsPanel, ToolsPanelItem } from '..'; import Panel from '../../panel'; import UnitControl from '../../unit-control'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; From ef78ee9f5c39108c328a69e2e9c99ca7927c8f9d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 18:28:48 +0100 Subject: [PATCH 03/21] Allow panelId to be null across all types --- .../src/tools-panel/tools-panel-item/README.md | 4 ++-- .../src/tools-panel/tools-panel/README.md | 2 +- packages/components/src/tools-panel/types.ts | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel-item/README.md b/packages/components/src/tools-panel/tools-panel-item/README.md index 85d03f96d1d04f..563057ec5cd7fd 100644 --- a/packages/components/src/tools-panel/tools-panel-item/README.md +++ b/packages/components/src/tools-panel/tools-panel-item/README.md @@ -58,10 +58,10 @@ A callback to take action when this item is selected in the `ToolsPanel` menu. - Required: No -### `panelId`: `string` +### `panelId`: `string | null` Panel items will ensure they are only registering with their intended panel by -comparing the `panelId` props set on both the item and the panel itself. This +comparing the `panelId` props set on both the item and the panel itself, or if the `panelId` is explicitly `null`. This allows items to be injected from a shared source. - Required: No diff --git a/packages/components/src/tools-panel/tools-panel/README.md b/packages/components/src/tools-panel/tools-panel/README.md index e04b774e9201bb..2fab44a04355cf 100644 --- a/packages/components/src/tools-panel/tools-panel/README.md +++ b/packages/components/src/tools-panel/tools-panel/README.md @@ -170,7 +170,7 @@ panel's dropdown menu. - Required: Yes -### `panelId`: `string` +### `panelId`: `string | null` If a `panelId` is set, it is passed through the `ToolsPanelContext` and used to restrict panel items. When a `panelId` is set, items can only register diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index ed846c900b18e1..57af53d3878ae1 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -34,10 +34,11 @@ export type ToolsPanelProps = { label: string; /** * If a `panelId` is set, it is passed through the `ToolsPanelContext` and - * used to restrict panel items. Only items with a matching `panelId` will - * be able to register themselves with this panel. + * used to restrict panel items. When a `panelId` is set, items can only + * register themselves if the `panelId` is explicitly `null` or the item's + * `panelId` matches exactly. */ - panelId: string; + panelId?: string | null; /** * A function to call when the `Reset all` menu option is selected. This is * passed through to the panel's header component. @@ -108,11 +109,12 @@ export type ToolsPanelItem = { */ label: string; /** - * Panel items will ensure they are only registering with their intended - * panel by comparing the `panelId` props set on both the item and the panel - * itself. This allows items to be injected from a shared source. + * Panel items will ensure they are only registering with their intended panel + * by comparing the `panelId` props set on both the item and the panel itself, + * or if the `panelId` is explicitly `null`. This allows items to be injected + * from a shared source. */ - panelId: string; + panelId?: string | null; /** * A `ToolsPanel` will collect each item's `resetAllFilter` and pass an * array of these functions through to the panel's `resetAll` callback. They @@ -145,7 +147,7 @@ export type ToolsPanelMenuItems = { }; export type ToolsPanelContext = { - panelId?: string; + panelId?: string | null; menuItems: ToolsPanelMenuItems; hasMenuItems: boolean; registerPanelItem: ( item: ToolsPanelItem ) => void; From e7c1977a994ee03cf0147fe656cedd6b0d6b0f65 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 18:41:12 +0100 Subject: [PATCH 04/21] `ToolsPanel`: mark `hasInnerWrapper` and `shouldRenderPlaceholderItems` as optional, assign `false` default value --- packages/components/src/tools-panel/tools-panel/README.md | 2 ++ packages/components/src/tools-panel/tools-panel/hook.ts | 4 ++-- packages/components/src/tools-panel/types.ts | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel/README.md b/packages/components/src/tools-panel/tools-panel/README.md index 2fab44a04355cf..b2c5eb4131f325 100644 --- a/packages/components/src/tools-panel/tools-panel/README.md +++ b/packages/components/src/tools-panel/tools-panel/README.md @@ -155,6 +155,7 @@ Flags that the items in this ToolsPanel will be contained within an inner wrapper element allowing the panel to lay them out accordingly. - Required: No +- Default: `false` ### `headingLevel`: `1 | 2 | 3 | 4 | 5 | 6 | '1' | '2' | '3' | '4' | '5' | '6'` @@ -192,3 +193,4 @@ Advises the `ToolsPanel` that all of its `ToolsPanelItem` children should render placeholder content (instead of `null`) when they are toggled off and hidden. - Required: No +- Default: `false` diff --git a/packages/components/src/tools-panel/tools-panel/hook.ts b/packages/components/src/tools-panel/tools-panel/hook.ts index 38751ecb951ad1..66b91f0ee671f2 100644 --- a/packages/components/src/tools-panel/tools-panel/hook.ts +++ b/packages/components/src/tools-panel/tools-panel/hook.ts @@ -59,8 +59,8 @@ export function useToolsPanel( headingLevel = 2, resetAll, panelId, - hasInnerWrapper, - shouldRenderPlaceholderItems, + hasInnerWrapper = false, + shouldRenderPlaceholderItems = false, __experimentalFirstVisibleItemClass, __experimentalLastVisibleItemClass, ...otherProps diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index 57af53d3878ae1..d8a07385ee0f06 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -19,8 +19,10 @@ export type ToolsPanelProps = { /** * Flags that the items in this ToolsPanel will be contained within an inner * wrapper element allowing the panel to lay them out accordingly. + * + * @default false */ - hasInnerWrapper: boolean; + hasInnerWrapper?: boolean; /** * The heading level of the panel's header. * @@ -47,8 +49,10 @@ export type ToolsPanelProps = { /** * Advises the `ToolsPanel` that its child `ToolsPanelItem`s should render * placeholder content instead of null when they are toggled off and hidden. + * + * @default false */ - shouldRenderPlaceholderItems: boolean; + shouldRenderPlaceholderItems?: boolean; /** * Experimental prop allowing for a custom CSS class to be applied to the * first visible `ToolsPanelItem` within the `ToolsPanel`. From 25cd91fdb9986921180eed66fecace11aa501b41 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 18:41:50 +0100 Subject: [PATCH 05/21] `ToolsPanelItem`: mark `resetAllFilter` as optional, assign `noop` default value --- .../components/src/tools-panel/tools-panel-item/README.md | 1 + packages/components/src/tools-panel/tools-panel-item/hook.ts | 4 +++- packages/components/src/tools-panel/types.ts | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel-item/README.md b/packages/components/src/tools-panel/tools-panel-item/README.md index 563057ec5cd7fd..44aae2fc666b34 100644 --- a/packages/components/src/tools-panel/tools-panel-item/README.md +++ b/packages/components/src/tools-panel/tools-panel-item/README.md @@ -73,3 +73,4 @@ these functions through to the panel's `resetAll` callback. They can then be iterated over to perform additional tasks. - Required: No +- Default: `() => {}` diff --git a/packages/components/src/tools-panel/tools-panel-item/hook.ts b/packages/components/src/tools-panel/tools-panel-item/hook.ts index c0b5572fee0d17..2e3970cd3172a1 100644 --- a/packages/components/src/tools-panel/tools-panel-item/hook.ts +++ b/packages/components/src/tools-panel/tools-panel-item/hook.ts @@ -13,6 +13,8 @@ import { useContextSystem, WordPressComponentProps } from '../../ui/context'; import { useCx } from '../../utils/hooks/use-cx'; import type { ToolsPanelItemProps } from '../types'; +const noop = () => {}; + export function useToolsPanelItem( props: WordPressComponentProps< ToolsPanelItemProps, 'div' > ) { @@ -22,7 +24,7 @@ export function useToolsPanelItem( isShownByDefault, label, panelId, - resetAllFilter, + resetAllFilter = noop, onDeselect, onSelect, ...otherProps diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index d8a07385ee0f06..737f5e7f0714e3 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -123,8 +123,10 @@ export type ToolsPanelItem = { * A `ToolsPanel` will collect each item's `resetAllFilter` and pass an * array of these functions through to the panel's `resetAll` callback. They * can then be iterated over to perform additional tasks. + * + * @default noop */ - resetAllFilter: ResetAllFilter; + resetAllFilter?: ResetAllFilter; }; export type ToolsPanelItemProps = ToolsPanelItem & { From 1496c0e7afac9057687924dddaadb3ea07645fc3 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 18:44:39 +0100 Subject: [PATCH 06/21] `ToolsPanelItem`: change `isShownByDefault` to be optional, assign default `false` value --- .../components/src/tools-panel/tools-panel-item/README.md | 3 ++- packages/components/src/tools-panel/tools-panel-item/hook.ts | 2 +- packages/components/src/tools-panel/types.ts | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel-item/README.md b/packages/components/src/tools-panel/tools-panel-item/README.md index 44aae2fc666b34..9e261ddc59be0f 100644 --- a/packages/components/src/tools-panel/tools-panel-item/README.md +++ b/packages/components/src/tools-panel/tools-panel-item/README.md @@ -31,7 +31,8 @@ This prop identifies the current item as being displayed by default. This means it will show regardless of whether it has a value set or is toggled on in the panel's menu. -- Required: Yes +- Required: No +- Default: `false` ### `label`: `string` diff --git a/packages/components/src/tools-panel/tools-panel-item/hook.ts b/packages/components/src/tools-panel/tools-panel-item/hook.ts index 2e3970cd3172a1..84572fba451a5e 100644 --- a/packages/components/src/tools-panel/tools-panel-item/hook.ts +++ b/packages/components/src/tools-panel/tools-panel-item/hook.ts @@ -21,7 +21,7 @@ export function useToolsPanelItem( const { className, hasValue, - isShownByDefault, + isShownByDefault = false, label, panelId, resetAllFilter = noop, diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index 737f5e7f0714e3..93970179ec02d2 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -101,8 +101,10 @@ export type ToolsPanelItem = { * This prop identifies the current item as being displayed by default. This * means it will show regardless of whether it has a value set or is toggled * on in the panel's menu. + * + * @default false */ - isShownByDefault: boolean; + isShownByDefault?: boolean; /** * The supplied label is dual purpose. It is used as: * 1. the human-readable label for the panel's dropdown menu From cd98b4587d2bcf2db4bc215727bc86a5c9bf6dab Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 19:17:22 +0100 Subject: [PATCH 07/21] Storybook examples: add component and state types --- .../src/tools-panel/stories/index.tsx | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index 69a3988baf1530..d4900d9fba176f 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import type { ComponentProps, ReactText } from 'react'; import styled from '@emotion/styled'; /** @@ -30,10 +31,10 @@ export default { }; export const _default = () => { - const [ height, setHeight ] = useState(); - const [ minHeight, setMinHeight ] = useState(); - const [ width, setWidth ] = useState(); - const [ scale, setScale ] = useState(); + const [ height, setHeight ] = useState< string | undefined >(); + const [ minHeight, setMinHeight ] = useState< string | undefined >(); + const [ width, setWidth ] = useState< string | undefined >(); + const [ scale, setScale ] = useState< ReactText | undefined >(); const resetAll = () => { setHeight( undefined ); @@ -118,8 +119,8 @@ export const _default = () => { }; export const WithNonToolsPanelItems = () => { - const [ height, setHeight ] = useState(); - const [ width, setWidth ] = useState(); + const [ height, setHeight ] = useState< string | undefined >(); + const [ width, setWidth ] = useState< string | undefined >(); const resetAll = () => { setHeight( undefined ); @@ -167,10 +168,12 @@ export const WithNonToolsPanelItems = () => { ); }; -export const WithOptionalItemsPlusIcon = ( { isShownByDefault } ) => { - const [ height, setHeight ] = useState(); - const [ width, setWidth ] = useState(); - const [ minWidth, setMinWidth ] = useState(); +export const WithOptionalItemsPlusIcon = ( { + isShownByDefault, +}: Pick< ComponentProps< typeof ToolsPanelItem >, 'isShownByDefault' > ) => { + const [ height, setHeight ] = useState< string | undefined >(); + const [ width, setWidth ] = useState< string | undefined >(); + const [ minWidth, setMinWidth ] = useState< string | undefined >(); const resetAll = () => { setHeight( undefined ); @@ -236,10 +239,15 @@ const { Fill: ToolsPanelItems, Slot } = createSlotFill( 'ToolsPanelSlot' ); const panelId = 'unique-tools-panel-id'; export const WithSlotFillItems = () => { - const [ attributes, setAttributes ] = useState( {} ); + const [ attributes, setAttributes ] = useState< { + width?: string; + height?: string; + } >( {} ); const { width, height } = attributes; - const resetAll = ( resetFilters = [] ) => { + const resetAll: ComponentProps< typeof ToolsPanel >[ 'resetAll' ] = ( + resetFilters = [] + ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { @@ -252,7 +260,7 @@ export const WithSlotFillItems = () => { setAttributes( newAttributes ); }; - const updateAttribute = ( name, value ) => { + const updateAttribute = ( name: string, value?: any ) => { setAttributes( { ...attributes, [ name ]: value, @@ -321,10 +329,15 @@ export const WithSlotFillItems = () => { }; export const WithConditionalDefaultControl = () => { - const [ attributes, setAttributes ] = useState( {} ); + const [ attributes, setAttributes ] = useState< { + height?: string; + scale?: ReactText; + } >( {} ); const { height, scale } = attributes; - const resetAll = ( resetFilters = [] ) => { + const resetAll: ComponentProps< typeof ToolsPanel >[ 'resetAll' ] = ( + resetFilters = [] + ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { @@ -337,7 +350,7 @@ export const WithConditionalDefaultControl = () => { setAttributes( newAttributes ); }; - const updateAttribute = ( name, value ) => { + const updateAttribute = ( name: string, value?: any ) => { setAttributes( { ...attributes, [ name ]: value, @@ -405,10 +418,15 @@ export const WithConditionalDefaultControl = () => { }; export const WithConditionallyRenderedControl = () => { - const [ attributes, setAttributes ] = useState( {} ); + const [ attributes, setAttributes ] = useState< { + height?: string; + scale?: ReactText; + } >( {} ); const { height, scale } = attributes; - const resetAll = ( resetFilters = [] ) => { + const resetAll: ComponentProps< typeof ToolsPanel >[ 'resetAll' ] = ( + resetFilters = [] + ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { @@ -421,7 +439,7 @@ export const WithConditionallyRenderedControl = () => { setAttributes( newAttributes ); }; - const updateAttribute = ( name, value ) => { + const updateAttribute = ( name: string, value?: any ) => { setAttributes( { ...attributes, [ name ]: value, From c79f4f535bf888f1d349ad286d969f3a6df34ddf Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 19:17:46 +0100 Subject: [PATCH 08/21] Storybook example: ignore unsolved errors, add TODO notes --- packages/components/src/tools-panel/stories/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index d4900d9fba176f..d0ae20aaaf18dc 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -187,6 +187,8 @@ export const WithOptionalItemsPlusIcon = ( { { resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, + // TODO: the `ResetFilter` type doesn't specify any attributes + // and doesn't return any objects + // @ts-ignore ...resetFilter( newAttributes ), }; } ); @@ -343,6 +348,9 @@ export const WithConditionalDefaultControl = () => { resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, + // TODO: the `ResetFilter` type doesn't specify any attributes + // and doesn't return any objects + // @ts-ignore ...resetFilter( newAttributes ), }; } ); @@ -432,6 +440,9 @@ export const WithConditionallyRenderedControl = () => { resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, + // TODO: the `ResetFilter` type doesn't specify any attributes + // and doesn't return any objects + // @ts-ignore ...resetFilter( newAttributes ), }; } ); From 21ec6c681f1ebceaebdb229bbba3da5e417b0f32 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 4 Feb 2023 19:20:53 +0100 Subject: [PATCH 09/21] Add Storybook meta object --- .../src/tools-panel/stories/index.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index d0ae20aaaf18dc..8aceb17448064c 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -2,6 +2,7 @@ * External dependencies */ import type { ComponentProps, ReactText } from 'react'; +import type { ComponentMeta } from '@storybook/react'; import styled from '@emotion/styled'; /** @@ -25,10 +26,24 @@ import Panel from '../../panel'; import UnitControl from '../../unit-control'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; -export default { +const meta: ComponentMeta< typeof ToolsPanel > = { title: 'Components (Experimental)/ToolsPanel', component: ToolsPanel, + subcomponents: { + ToolsPanelItem, + }, + argTypes: { + // TODO + }, + parameters: { + actions: { argTypesRegex: '^on.*' }, + controls: { + expanded: true, + }, + docs: { source: { state: 'open' } }, + }, }; +export default meta; export const _default = () => { const [ height, setHeight ] = useState< string | undefined >(); From 38ad0a193634aebbb9eadfd70f8956627015262e Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sun, 5 Feb 2023 23:38:08 +0100 Subject: [PATCH 10/21] Fix type errors in Storybook examples --- .../src/tools-panel/stories/index.tsx | 133 ++++++++++++------ .../tools-panel-with-item-group-slot.tsx | 83 +++++++++-- 2 files changed, 157 insertions(+), 59 deletions(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index 8aceb17448064c..9b6de87990a8b2 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -1,8 +1,7 @@ /** * External dependencies */ -import type { ComponentProps, ReactText } from 'react'; -import type { ComponentMeta } from '@storybook/react'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; import styled from '@emotion/styled'; /** @@ -33,7 +32,10 @@ const meta: ComponentMeta< typeof ToolsPanel > = { ToolsPanelItem, }, argTypes: { - // TODO + as: { control: { type: null } }, + children: { control: { type: null } }, + panelId: { control: { type: null } }, + resetAll: { action: 'resetAll' }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -45,26 +47,27 @@ const meta: ComponentMeta< typeof ToolsPanel > = { }; export default meta; -export const _default = () => { +export const Default: ComponentStory< typeof ToolsPanel > = ( { + resetAll: resetAllProp, + ...props +} ) => { const [ height, setHeight ] = useState< string | undefined >(); const [ minHeight, setMinHeight ] = useState< string | undefined >(); const [ width, setWidth ] = useState< string | undefined >(); - const [ scale, setScale ] = useState< ReactText | undefined >(); + const [ scale, setScale ] = useState< React.ReactText | undefined >(); - const resetAll = () => { + const resetAll: typeof resetAllProp = ( filters ) => { setHeight( undefined ); setWidth( undefined ); setMinHeight( undefined ); setScale( undefined ); + resetAllProp( filters ); }; return ( - + !! width } label="Width" @@ -132,23 +135,27 @@ export const _default = () => { ); }; +Default.args = { + label: 'Tools Panel (default example)', +}; -export const WithNonToolsPanelItems = () => { +export const WithNonToolsPanelItems: ComponentStory< typeof ToolsPanel > = ( { + resetAll: resetAllProp, + ...props +} ) => { const [ height, setHeight ] = useState< string | undefined >(); const [ width, setWidth ] = useState< string | undefined >(); - const resetAll = () => { + const resetAll: typeof resetAllProp = ( filters ) => { setHeight( undefined ); setWidth( undefined ); + resetAll( filters ); }; return ( - + This text illustrates not all items must be wrapped in a ToolsPanelItem and represented in the panel menu. @@ -182,35 +189,47 @@ export const WithNonToolsPanelItems = () => { ); }; +WithNonToolsPanelItems.args = { + ...Default.args, + label: 'ToolsPanel (with non-menu items)', +}; -export const WithOptionalItemsPlusIcon = ( { - isShownByDefault, -}: Pick< ComponentProps< typeof ToolsPanelItem >, 'isShownByDefault' > ) => { +export const WithOptionalItemsPlusIcon: ComponentStory< + typeof ToolsPanel +> = ( { + // This is an extra prop that is passed to one underlying `ToolsPanelItem` to + // showcase the differences in `ToolsPanel` when all items are optional or not + // @ts-expect-error + isFirstToolsPanelItemShownByDefault, + resetAll: resetAllProp, + ...props +} ) => { const [ height, setHeight ] = useState< string | undefined >(); const [ width, setWidth ] = useState< string | undefined >(); const [ minWidth, setMinWidth ] = useState< string | undefined >(); - const resetAll = () => { + const resetAll: typeof resetAllProp = ( filters ) => { setHeight( undefined ); setWidth( undefined ); setMinWidth( undefined ); + resetAll( filters ); }; return ( !! minWidth } label="Minimum width" onDeselect={ () => setMinWidth( undefined ) } - isShownByDefault={ isShownByDefault } + isShownByDefault={ isFirstToolsPanelItemShownByDefault } > { +export const WithSlotFillItems: ComponentStory< typeof ToolsPanel > = ( { + resetAll: resetAllProp, + panelId, + ...props +} ) => { const [ attributes, setAttributes ] = useState< { width?: string; height?: string; } >( {} ); const { width, height } = attributes; - const resetAll: ComponentProps< typeof ToolsPanel >[ 'resetAll' ] = ( - resetFilters = [] - ) => { + const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { @@ -278,6 +303,7 @@ export const WithSlotFillItems = () => { } ); setAttributes( newAttributes ); + resetAllProp( resetFilters ); }; const updateAttribute = ( name: string, value?: any ) => { @@ -336,7 +362,7 @@ export const WithSlotFillItems = () => { @@ -347,17 +373,22 @@ export const WithSlotFillItems = () => { ); }; +WithSlotFillItems.args = { + ...Default.args, + label: 'Tools Panel With SlotFill Items', + panelId: 'unique-tools-panel-id', +}; -export const WithConditionalDefaultControl = () => { +export const WithConditionalDefaultControl: ComponentStory< + typeof ToolsPanel +> = ( { resetAll: resetAllProp, panelId, ...props } ) => { const [ attributes, setAttributes ] = useState< { height?: string; - scale?: ReactText; + scale?: React.ReactText; } >( {} ); const { height, scale } = attributes; - const resetAll: ComponentProps< typeof ToolsPanel >[ 'resetAll' ] = ( - resetFilters = [] - ) => { + const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { @@ -371,6 +402,8 @@ export const WithConditionalDefaultControl = () => { } ); setAttributes( newAttributes ); + + resetAllProp( resetFilters ); }; const updateAttribute = ( name: string, value?: any ) => { @@ -428,7 +461,7 @@ export const WithConditionalDefaultControl = () => { @@ -439,17 +472,22 @@ export const WithConditionalDefaultControl = () => { ); }; +WithConditionalDefaultControl.args = { + ...Default.args, + label: 'Tools Panel With Conditional Default via SlotFill', + panelId: 'unique-tools-panel-id', +}; -export const WithConditionallyRenderedControl = () => { +export const WithConditionallyRenderedControl: ComponentStory< + typeof ToolsPanel +> = ( { resetAll: resetAllProp, panelId, ...props } ) => { const [ attributes, setAttributes ] = useState< { height?: string; - scale?: ReactText; + scale?: React.ReactText; } >( {} ); const { height, scale } = attributes; - const resetAll: ComponentProps< typeof ToolsPanel >[ 'resetAll' ] = ( - resetFilters = [] - ) => { + const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { @@ -463,6 +501,8 @@ export const WithConditionallyRenderedControl = () => { } ); setAttributes( newAttributes ); + + resetAllProp( resetFilters ); }; const updateAttribute = ( name: string, value?: any ) => { @@ -533,7 +573,7 @@ export const WithConditionallyRenderedControl = () => { @@ -544,6 +584,11 @@ export const WithConditionallyRenderedControl = () => { ); }; +WithConditionallyRenderedControl.args = { + ...Default.args, + label: 'Tools Panel With Conditionally Rendered Item via SlotFill', + panelId: 'unique-tools-panel-id', +}; export { ToolsPanelWithItemGroupSlot } from './utils/tools-panel-with-item-group-slot'; diff --git a/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx index d1895324df4c3b..fb83108af34e83 100644 --- a/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx +++ b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx @@ -3,6 +3,7 @@ */ import styled from '@emotion/styled'; import { css } from '@emotion/react'; +import type { ComponentStory } from '@storybook/react'; /** * WordPress dependencies @@ -21,6 +22,7 @@ import { FlexItem } from '../../../flex'; import { HStack } from '../../../h-stack'; import { Item, ItemGroup } from '../../../item-group'; import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '../..'; +import type { ToolsPanelContext as ToolsPanelContextType } from '../../types'; import { createSlotFill, Provider as SlotFillProvider, @@ -45,7 +47,6 @@ const colors = [ { name: 'Yellow 10', color: '#f2d675' }, { name: 'Yellow 40', color: '#bd8600' }, ]; -const panelId = 'unique-tools-panel-id'; const { Fill, Slot } = createSlotFill( 'ToolsPanelSlot' ); @@ -62,10 +63,10 @@ const { Fill, Slot } = createSlotFill( 'ToolsPanelSlot' ); // This custom fill is required to re-establish the ToolsPanelContext for // injected ToolsPanelItem components as they will not have access to the React // Context as the Provider is part of the ToolsPanelItems.Slot tree. -const ToolsPanelItems = ( { children } ) => { +const ToolsPanelItems = ( { children }: { children: React.ReactNode } ) => { return ( - { ( fillProps ) => ( + { ( fillProps: ToolsPanelContextType ) => ( { children } @@ -76,7 +77,13 @@ const ToolsPanelItems = ( { children } ) => { // This fetches the ToolsPanelContext and passes it through `fillProps` so that // rendered fills can re-establish the `ToolsPanelContext.Provider`. -const SlotContainer = ( { Slot: ToolsPanelSlot, ...props } ) => { +const SlotContainer = ( { + Slot: ToolsPanelSlot, + ...props +}: { + Slot: React.ElementType; + [ key: string ]: any; +} ) => { const toolsPanelContext = useContext( ToolsPanelContext ); return ( @@ -90,49 +97,83 @@ const SlotContainer = ( { Slot: ToolsPanelSlot, ...props } ) => { // This wraps the slot with a `ToolsPanel` mimicking a real-world use case from // the block editor. -ToolsPanelItems.Slot = ( { resetAll, ...props } ) => ( +ToolsPanelItems.Slot = ( { + resetAll, + panelId, + label, + hasInnerWrapper, + shouldRenderPlaceholderItems, + __experimentalFirstVisibleItemClass, + __experimentalLastVisibleItemClass, + ...props +}: React.ComponentProps< typeof ToolsPanel > & { + [ key: string ]: any; +} ) => ( ); -export const ToolsPanelWithItemGroupSlot = () => { - const [ attributes, setAttributes ] = useState( {} ); +export const ToolsPanelWithItemGroupSlot: ComponentStory< + typeof ToolsPanel +> = ( { resetAll: resetAllProp, panelId, ...props } ) => { + const [ attributes, setAttributes ] = useState< { + text?: string; + background?: string; + link?: string; + } >( {} ); const { text, background, link } = attributes; const cx = useCx(); const slotWrapperClassName = cx( SlotWrapper ); const itemClassName = cx( ToolsPanelItemClass ); - const resetAll = ( resetFilters = [] ) => { + const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { let newAttributes = {}; resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, + // TODO: the `ResetFilter` type doesn't specify any attributes + // and doesn't return any objects + // @ts-ignore ...resetFilter( newAttributes ), }; } ); setAttributes( newAttributes ); + + resetAllProp( resetFilters ); }; - const updateAttribute = ( name, value ) => { + const updateAttribute = ( name: string, value?: any ) => { setAttributes( { ...attributes, [ name ]: value, } ); }; - const ToolsPanelColorDropdown = ( { attribute, label, value } ) => { + const ToolsPanelColorDropdown = ( { + attribute, + label, + value, + }: { + attribute: string; + label: string; + value?: string; + } ) => { return ( { @@ -204,6 +249,14 @@ export const ToolsPanelWithItemGroupSlot = () => { ); }; +ToolsPanelWithItemGroupSlot.args = { + label: 'Tools Panel with Item Group', + panelId: 'unique-tools-panel-id', + hasInnerWrapper: true, + shouldRenderPlaceholderItems: true, + __experimentalFirstVisibleItemClass: 'first', + __experimentalLastVisibleItemClass: 'last', +}; const PanelWrapperView = styled.div` font-size: 13px; From e174f5a4681c98db39a40b60f1cf51755830cf3a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 9 Feb 2023 22:05:37 +0100 Subject: [PATCH 11/21] Remove extraneous `isFirstToolsPanelItemShownByDefault` prop from Story args --- .../src/tools-panel/stories/index.tsx | 131 ++++++++++-------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index 9b6de87990a8b2..db4476f6d477de 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -196,14 +196,11 @@ WithNonToolsPanelItems.args = { export const WithOptionalItemsPlusIcon: ComponentStory< typeof ToolsPanel -> = ( { - // This is an extra prop that is passed to one underlying `ToolsPanelItem` to - // showcase the differences in `ToolsPanel` when all items are optional or not - // @ts-expect-error - isFirstToolsPanelItemShownByDefault, - resetAll: resetAllProp, - ...props -} ) => { +> = ( { resetAll: resetAllProp, ...props } ) => { + const [ + isFirstToolsPanelItemShownByDefault, + setIsFirstToolsPanelItemShownByDefault, + ] = useState( false ); const [ height, setHeight ] = useState< string | undefined >(); const [ width, setWidth ] = useState< string | undefined >(); const [ minWidth, setMinWidth ] = useState< string | undefined >(); @@ -216,64 +213,86 @@ export const WithOptionalItemsPlusIcon: ComponentStory< }; return ( - - - - !! minWidth } - label="Minimum width" - onDeselect={ () => setMinWidth( undefined ) } - isShownByDefault={ isFirstToolsPanelItemShownByDefault } + <> + + + - !! minWidth } label="Minimum width" - value={ minWidth } - onChange={ ( next ) => setMinWidth( next ) } - /> - - !! width } - label="Width" - onDeselect={ () => setWidth( undefined ) } - isShownByDefault={ false } - > - setMinWidth( undefined ) } + isShownByDefault={ + isFirstToolsPanelItemShownByDefault + } + > + setMinWidth( next ) } + /> + + !! width } label="Width" - value={ width } - onChange={ ( next ) => setWidth( next ) } - /> - - !! height } - label="Height" - onDeselect={ () => setHeight( undefined ) } - isShownByDefault={ false } - > - setWidth( undefined ) } + isShownByDefault={ false } + > + setWidth( next ) } + /> + + !! height } label="Height" - value={ height } - onChange={ ( next ) => setHeight( next ) } - /> - - - - + onDeselect={ () => setHeight( undefined ) } + isShownByDefault={ false } + > + setHeight( next ) } + /> + + + + + + + ); }; WithOptionalItemsPlusIcon.args = { ...Default.args, label: 'Tools Panel (optional items only)', - // This is an extra prop that is passed to one underlying `ToolsPanelItem` to - // showcase the differences in `ToolsPanel` when all items are optional or not - // @ts-expect-error - isFirstToolsPanelItemShownByDefault: false, }; const { Fill: ToolsPanelItems, Slot } = createSlotFill( 'ToolsPanelSlot' ); From 0df37ca4298a82781ac5ba3682f1c8bdc52c272a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 9 Feb 2023 22:07:33 +0100 Subject: [PATCH 12/21] CHANGELOG --- packages/components/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d7e78770b56c07..55b00264cd1c2a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,10 @@ - `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)). +### Bug fix + +- `ToolsPanel`: fix type inconsistencies between types, docs and normal component usage ([47944](https://github.com/WordPress/gutenberg/pull/47944)). + ### Internal - `NavigatorButton`: Reuse `Button` types ([47754](https://github.com/WordPress/gutenberg/pull/47754)). @@ -28,6 +32,7 @@ - `ToolsPanel`: Allow display of optional items when values are updated externally to item controls ([47727](https://github.com/WordPress/gutenberg/pull/47727)). - `ToolsPanel`: Ensure display of optional items when values are updated externally and multiple blocks selected ([47864](https://github.com/WordPress/gutenberg/pull/47864)). - `Navigator`: add more pattern matching tests, refine existing tests ([47910](https://github.com/WordPress/gutenberg/pull/47910)). +- `ToolsPanel`: Refactor Storybook examples to TypeScript ([47944](https://github.com/WordPress/gutenberg/pull/47944)). ## 23.3.0 (2023-02-01) From 2780f5d8d0b029b96b7d149af5a32c7cd8b9ae81 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 10 Feb 2023 12:24:12 +0100 Subject: [PATCH 13/21] Fix resetAll prop forwarding typo --- packages/components/src/tools-panel/stories/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index db4476f6d477de..1b55a0f81d5d04 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -149,7 +149,7 @@ export const WithNonToolsPanelItems: ComponentStory< typeof ToolsPanel > = ( { const resetAll: typeof resetAllProp = ( filters ) => { setHeight( undefined ); setWidth( undefined ); - resetAll( filters ); + resetAllProp( filters ); }; return ( @@ -209,7 +209,7 @@ export const WithOptionalItemsPlusIcon: ComponentStory< setHeight( undefined ); setWidth( undefined ); setMinWidth( undefined ); - resetAll( filters ); + resetAllProp( filters ); }; return ( From fa0f4cf3289441a2d4b3bf5d934791698903a9d6 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 10 Feb 2023 12:41:56 +0100 Subject: [PATCH 14/21] Update `ResetAllFilter` type to receive and return data. Update READMEs --- packages/components/src/tools-panel/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index 93970179ec02d2..5314e6ef538576 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -8,7 +8,7 @@ import type { ReactNode } from 'react'; */ import type { HeadingSize } from '../heading/types'; -type ResetAllFilter = () => void; +type ResetAllFilter = ( attributes?: any ) => any; type ResetAll = ( filters?: ResetAllFilter[] ) => void; export type ToolsPanelProps = { From 02528f6f981480232bd5305e4164a2e8fc1b49b2 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 10 Feb 2023 12:43:00 +0100 Subject: [PATCH 15/21] Update READMEs to sync `resetAll` and `resetAllFilter` types and descriptions --- .../components/src/tools-panel/tools-panel-item/README.md | 2 +- packages/components/src/tools-panel/tools-panel/README.md | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel-item/README.md b/packages/components/src/tools-panel/tools-panel-item/README.md index 9e261ddc59be0f..91f9c78ff9cbe8 100644 --- a/packages/components/src/tools-panel/tools-panel-item/README.md +++ b/packages/components/src/tools-panel/tools-panel-item/README.md @@ -67,7 +67,7 @@ allows items to be injected from a shared source. - Required: No -### `resetAllFilter`: `() => void` +### `resetAllFilter`: `( attributes?: any ) => any` A `ToolsPanel` will collect each item's `resetAllFilter` and pass an array of these functions through to the panel's `resetAll` callback. They can then be diff --git a/packages/components/src/tools-panel/tools-panel/README.md b/packages/components/src/tools-panel/tools-panel/README.md index b2c5eb4131f325..6802a6436875ad 100644 --- a/packages/components/src/tools-panel/tools-panel/README.md +++ b/packages/components/src/tools-panel/tools-panel/README.md @@ -180,10 +180,9 @@ exactly. - Required: No -### `resetAll`: `() => void` +### `resetAll`: `( filters?: ResetAllFilter[] ) => void` -A function to call when the `Reset all` menu option is selected. This is passed -through to the panel's header component. +A function to call when the `Reset all` menu option is selected. As an argument, it receives an array containing the `resetAllFilter` callbacks of all the valid registered `ToolsPanelItems`. - Required: Yes From 82f83bf1da48495b2d1407a88074b2dc9ee7d49b Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 10 Feb 2023 12:43:31 +0100 Subject: [PATCH 16/21] Remove @ts-ignore comments after updating the `ResetAllFilter` type --- .../components/src/tools-panel/stories/index.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index 1b55a0f81d5d04..4ad81711b2fe44 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -309,14 +309,11 @@ export const WithSlotFillItems: ComponentStory< typeof ToolsPanel > = ( { const { width, height } = attributes; const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { - let newAttributes = {}; + let newAttributes: typeof attributes = {}; resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, - // TODO: the `ResetFilter` type doesn't specify any attributes - // and doesn't return any objects - // @ts-ignore ...resetFilter( newAttributes ), }; } ); @@ -408,14 +405,11 @@ export const WithConditionalDefaultControl: ComponentStory< const { height, scale } = attributes; const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { - let newAttributes = {}; + let newAttributes: typeof attributes = {}; resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, - // TODO: the `ResetFilter` type doesn't specify any attributes - // and doesn't return any objects - // @ts-ignore ...resetFilter( newAttributes ), }; } ); @@ -507,14 +501,11 @@ export const WithConditionallyRenderedControl: ComponentStory< const { height, scale } = attributes; const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { - let newAttributes = {}; + let newAttributes: typeof attributes = {}; resetFilters.forEach( ( resetFilter ) => { newAttributes = { ...newAttributes, - // TODO: the `ResetFilter` type doesn't specify any attributes - // and doesn't return any objects - // @ts-ignore ...resetFilter( newAttributes ), }; } ); From 3577c506f720d2f4c613ca9cc8e89f28149d7279 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 10 Feb 2023 12:45:37 +0100 Subject: [PATCH 17/21] Remove unnecessary `ToolsPanelWithItemGroupSlot` story --- .../src/tools-panel/stories/index.tsx | 2 - .../tools-panel-with-item-group-slot.tsx | 299 ------------------ 2 files changed, 301 deletions(-) delete mode 100644 packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx diff --git a/packages/components/src/tools-panel/stories/index.tsx b/packages/components/src/tools-panel/stories/index.tsx index 4ad81711b2fe44..52c89a6ece5d66 100644 --- a/packages/components/src/tools-panel/stories/index.tsx +++ b/packages/components/src/tools-panel/stories/index.tsx @@ -600,8 +600,6 @@ WithConditionallyRenderedControl.args = { panelId: 'unique-tools-panel-id', }; -export { ToolsPanelWithItemGroupSlot } from './utils/tools-panel-with-item-group-slot'; - const PanelWrapperView = styled.div` font-size: 13px; diff --git a/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx deleted file mode 100644 index fb83108af34e83..00000000000000 --- a/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.tsx +++ /dev/null @@ -1,299 +0,0 @@ -/** - * External dependencies - */ -import styled from '@emotion/styled'; -import { css } from '@emotion/react'; -import type { ComponentStory } from '@storybook/react'; - -/** - * WordPress dependencies - */ -import { useContext, useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import Button from '../../../button'; -import ColorIndicator from '../../../color-indicator'; -import ColorPalette from '../../../color-palette'; -import Dropdown from '../../../dropdown'; -import Panel from '../../../panel'; -import { FlexItem } from '../../../flex'; -import { HStack } from '../../../h-stack'; -import { Item, ItemGroup } from '../../../item-group'; -import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '../..'; -import type { ToolsPanelContext as ToolsPanelContextType } from '../../types'; -import { - createSlotFill, - Provider as SlotFillProvider, -} from '../../../slot-fill'; -import { useCx } from '../../../utils'; - -// Available border colors. -const colors = [ - { name: 'Gray 0', color: '#f6f7f7' }, - { name: 'Gray 5', color: '#dcdcde' }, - { name: 'Gray 20', color: '#a7aaad' }, - { name: 'Gray 70', color: '#3c434a' }, - { name: 'Gray 100', color: '#101517' }, - { name: 'Blue 20', color: '#72aee6' }, - { name: 'Blue 40', color: '#3582c4' }, - { name: 'Blue 70', color: '#0a4b78' }, - { name: 'Red 40', color: '#e65054' }, - { name: 'Red 70', color: '#8a2424' }, - { name: 'Green 10', color: '#68de7c' }, - { name: 'Green 40', color: '#00a32a' }, - { name: 'Green 60', color: '#007017' }, - { name: 'Yellow 10', color: '#f2d675' }, - { name: 'Yellow 40', color: '#bd8600' }, -]; - -const { Fill, Slot } = createSlotFill( 'ToolsPanelSlot' ); - -// This storybook example aims to replicate a virtual bubbling SlotFill use case -// for the `ToolsPanel` when the Slot itself is an `ItemGroup`. - -// In this scenario the `ToolsPanel` has to render item placeholders so fills -// maintain their order in the DOM. These placeholders in the DOM prevent the -// normal styling of the `ItemGroup` in particular the border radii on the first -// and last items. In case consumers of the ItemGroup and ToolsPanel are -// applying their own styles to these components, the ToolsPanel needs to assist -// consumers in identifying which of its visible items are first and last. - -// This custom fill is required to re-establish the ToolsPanelContext for -// injected ToolsPanelItem components as they will not have access to the React -// Context as the Provider is part of the ToolsPanelItems.Slot tree. -const ToolsPanelItems = ( { children }: { children: React.ReactNode } ) => { - return ( - - { ( fillProps: ToolsPanelContextType ) => ( - - { children } - - ) } - - ); -}; - -// This fetches the ToolsPanelContext and passes it through `fillProps` so that -// rendered fills can re-establish the `ToolsPanelContext.Provider`. -const SlotContainer = ( { - Slot: ToolsPanelSlot, - ...props -}: { - Slot: React.ElementType; - [ key: string ]: any; -} ) => { - const toolsPanelContext = useContext( ToolsPanelContext ); - - return ( - - ); -}; - -// This wraps the slot with a `ToolsPanel` mimicking a real-world use case from -// the block editor. -ToolsPanelItems.Slot = ( { - resetAll, - panelId, - label, - hasInnerWrapper, - shouldRenderPlaceholderItems, - __experimentalFirstVisibleItemClass, - __experimentalLastVisibleItemClass, - ...props -}: React.ComponentProps< typeof ToolsPanel > & { - [ key: string ]: any; -} ) => ( - - - -); - -export const ToolsPanelWithItemGroupSlot: ComponentStory< - typeof ToolsPanel -> = ( { resetAll: resetAllProp, panelId, ...props } ) => { - const [ attributes, setAttributes ] = useState< { - text?: string; - background?: string; - link?: string; - } >( {} ); - const { text, background, link } = attributes; - - const cx = useCx(); - const slotWrapperClassName = cx( SlotWrapper ); - const itemClassName = cx( ToolsPanelItemClass ); - - const resetAll: typeof resetAllProp = ( resetFilters = [] ) => { - let newAttributes = {}; - - resetFilters.forEach( ( resetFilter ) => { - newAttributes = { - ...newAttributes, - // TODO: the `ResetFilter` type doesn't specify any attributes - // and doesn't return any objects - // @ts-ignore - ...resetFilter( newAttributes ), - }; - } ); - - setAttributes( newAttributes ); - - resetAllProp( resetFilters ); - }; - - const updateAttribute = ( name: string, value?: any ) => { - setAttributes( { - ...attributes, - [ name ]: value, - } ); - }; - - const ToolsPanelColorDropdown = ( { - attribute, - label, - value, - }: { - attribute: string; - label: string; - value?: string; - } ) => { - return ( - !! value } - label={ label } - onDeselect={ () => updateAttribute( attribute, undefined ) } - resetAllFilter={ () => ( { [ attribute ]: undefined } ) } - panelId={ panelId } - as={ Item } - > - ( - - ) } - renderContent={ () => ( - - updateAttribute( attribute, newColor ) - } - /> - ) } - /> - - ); - }; - - // ToolsPanelItems are rendered via two different fills to simulate - // injection from multiple locations. - return ( - - - - - - - - - - - - - - - ); -}; -ToolsPanelWithItemGroupSlot.args = { - label: 'Tools Panel with Item Group', - panelId: 'unique-tools-panel-id', - hasInnerWrapper: true, - shouldRenderPlaceholderItems: true, - __experimentalFirstVisibleItemClass: 'first', - __experimentalLastVisibleItemClass: 'last', -}; - -const PanelWrapperView = styled.div` - font-size: 13px; - - .components-dropdown-menu__menu { - max-width: 220px; - } -`; - -const SlotWrapper = css` - &&& { - row-gap: 0; - border-radius: 20px; - } - - > div { - grid-column: span 2; - border-radius: inherit; - } -`; - -const ToolsPanelItemClass = css` - padding: 0; - - &&.first { - border-top-left-radius: inherit; - border-top-right-radius: inherit; - } - - &.last { - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; - border-bottom-color: transparent; - } - && > div, - && > div > button { - width: 100%; - border-radius: inherit; - } -`; From 4aad2e27cc270902630c4b92188eaebf265c856b Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 10 Feb 2023 12:51:31 +0100 Subject: [PATCH 18/21] update `resetAll` prop descritption in types JSDocs --- packages/components/src/tools-panel/types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index 5314e6ef538576..86a8c1579b9b55 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -42,8 +42,9 @@ export type ToolsPanelProps = { */ panelId?: string | null; /** - * A function to call when the `Reset all` menu option is selected. This is - * passed through to the panel's header component. + * A function to call when the `Reset all` menu option is selected. As an + * argument, it receives an array containing the `resetAllFilter` callbacks + * of all the valid registered `ToolsPanelItems`. */ resetAll: ResetAll; /** From 8673259f8d206459f38e9f03043c3ccf55d175e8 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:54:20 +1000 Subject: [PATCH 19/21] Fix ToolsPanel component exports --- .../src/tools-panel/tools-panel-item/component.tsx | 8 ++++---- .../components/src/tools-panel/tools-panel/component.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel-item/component.tsx b/packages/components/src/tools-panel/tools-panel-item/component.tsx index b0cc12c0f19fde..f66d3845ff0832 100644 --- a/packages/components/src/tools-panel/tools-panel-item/component.tsx +++ b/packages/components/src/tools-panel/tools-panel-item/component.tsx @@ -13,7 +13,7 @@ import type { ToolsPanelItemProps } from '../types'; // This wraps controls to be conditionally displayed within a tools panel. It // prevents props being applied to HTML elements that would make them invalid. -const ToolsPanelItem = ( +const UnconnectedToolsPanelItem = ( props: WordPressComponentProps< ToolsPanelItemProps, 'div' >, forwardedRef: ForwardedRef< any > ) => { @@ -37,9 +37,9 @@ const ToolsPanelItem = ( ); }; -const ConnectedToolsPanelItem = contextConnect( - ToolsPanelItem, +export const ToolsPanelItem = contextConnect( + UnconnectedToolsPanelItem, 'ToolsPanelItem' ); -export default ConnectedToolsPanelItem; +export default ToolsPanelItem; diff --git a/packages/components/src/tools-panel/tools-panel/component.tsx b/packages/components/src/tools-panel/tools-panel/component.tsx index 929868e3f1f8c5..7152f0a909a34f 100644 --- a/packages/components/src/tools-panel/tools-panel/component.tsx +++ b/packages/components/src/tools-panel/tools-panel/component.tsx @@ -13,7 +13,7 @@ import { Grid } from '../../grid'; import { contextConnect, WordPressComponentProps } from '../../ui/context'; import type { ToolsPanelProps } from '../types'; -const ToolsPanel = ( +const UnconnectedToolsPanel = ( props: WordPressComponentProps< ToolsPanelProps, 'div' >, forwardedRef: ForwardedRef< any > ) => { @@ -42,6 +42,6 @@ const ToolsPanel = ( ); }; -const ConnectedToolsPanel = contextConnect( ToolsPanel, 'ToolsPanel' ); +export const ToolsPanel = contextConnect( UnconnectedToolsPanel, 'ToolsPanel' ); -export default ConnectedToolsPanel; +export default ToolsPanel; From 86d61c71444c9b60ef563440b402725cb0aa5b7f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:55:34 +1000 Subject: [PATCH 20/21] Draft ToolsPanel JSDoc example comment --- .../src/tools-panel/tools-panel/component.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/components/src/tools-panel/tools-panel/component.tsx b/packages/components/src/tools-panel/tools-panel/component.tsx index 7152f0a909a34f..c6f3a9ce5469d6 100644 --- a/packages/components/src/tools-panel/tools-panel/component.tsx +++ b/packages/components/src/tools-panel/tools-panel/component.tsx @@ -42,6 +42,58 @@ const UnconnectedToolsPanel = ( ); }; +/** + * The `ToolsPanel` is a container component that displays its children preceded + * by a header. The header includes a dropdown menu which is automatically + * generated from the panel's inner `ToolsPanelItems`. + * + * @example + * ```jsx + * import { __ } from '@wordpress/i18n'; + * import { + * __experimentalToolsPanel as ToolsPanel, + * __experimentalToolsPanelItem as ToolsPanelItem, + * __experimentalUnitControl as UnitControl + * } from '@wordpress/components'; + * + * function Example() { + * const [ height, setHeight ] = useState(); + * const [ width, setWidth ] = useState(); + * + * const resetAll = () => { + * setHeight(); + * setWidth(); + * } + * + * return ( + * + * !! height } + * label={ __( 'Height' ) } + * onDeselect={ () => setHeight() } + * > + * + * + * !! width } + * label={ __( 'Width' ) } + * onDeselect={ () => setWidth() } + * > + * + * + * + * ); + * } + * ``` + */ export const ToolsPanel = contextConnect( UnconnectedToolsPanel, 'ToolsPanel' ); export default ToolsPanel; From c6ecc6bccd4d8b1cff2f7a3bac0c8058df23827f Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 13 Feb 2023 09:09:56 +0100 Subject: [PATCH 21/21] Update CHANGELOG to avoid duplicate sections --- packages/components/CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 55b00264cd1c2a..24b8589aacef4c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug Fix +- `ToolsPanel`: fix type inconsistencies between types, docs and normal component usage ([47944](https://github.com/WordPress/gutenberg/pull/47944)). - `SelectControl`: Fix styling when `multiple` prop is enabled ([#47893](https://github.com/WordPress/gutenberg/pull/43213)). ### Enhancements @@ -12,10 +13,6 @@ - `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)). -### Bug fix - -- `ToolsPanel`: fix type inconsistencies between types, docs and normal component usage ([47944](https://github.com/WordPress/gutenberg/pull/47944)). - ### Internal - `NavigatorButton`: Reuse `Button` types ([47754](https://github.com/WordPress/gutenberg/pull/47754)).