diff --git a/docs/manifest.json b/docs/manifest.json index dca4d7d955b5b2..a06a68b96f93a5 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -737,6 +737,42 @@ "markdown_source": "../packages/components/src/confirm-dialog/README.md", "parent": "components" }, + { + "title": "CustomSelectControlArrow", + "slug": "custom-select-control-arrow", + "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md", + "parent": "components" + }, + { + "title": "CustomSelectControlGroupLabel", + "slug": "custom-select-control-group-label", + "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md", + "parent": "components" + }, + { + "title": "CustomSelectControlGroup", + "slug": "custom-select-control-group", + "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-group/README.md", + "parent": "components" + }, + { + "title": "CustomSelectControlItem", + "slug": "custom-select-control-item", + "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-item/README.md", + "parent": "components" + }, + { + "title": "CustomSelectControlSeparator", + "slug": "custom-select-control-separator", + "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-separator/README.md", + "parent": "components" + }, + { + "title": "CustomSelectControl", + "slug": "custom-select-control", + "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control/README.md", + "parent": "components" + }, { "title": "CustomSelectControl", "slug": "custom-select-control", diff --git a/package-lock.json b/package-lock.json index afbbfeaa5f8e66..29a2076b26ecfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16699,6 +16699,7 @@ "@wordpress/primitives": "file:packages/primitives", "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/warning": "file:packages/warning", + "ariakit": "2.0.0-next.36", "classnames": "^2.3.1", "colord": "^2.7.0", "dom-scroll-into-view": "^1.2.1", @@ -16729,6 +16730,33 @@ "csstype": "^3.0.2" } }, + "@floating-ui/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.0.tgz", + "integrity": "sha512-sm3nW0hHAxTv3gRDdCH8rNVQxijF+qPFo5gAeXCErRjKC7Qc28lIQ3R9Vd7Gw+KgwfA7RhRydDFuGeI0peGq7A==" + }, + "@floating-ui/dom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.0.tgz", + "integrity": "sha512-PMqJvY5Fae8HVQgUqM+lidprS6p9LSvB0AUhCdYKqr3YCaV+WaWCeVNBtXPRY2YIdrgcsL2+vd5F07FxgihHUw==", + "requires": { + "@floating-ui/core": "^1.0.0" + } + }, + "ariakit": { + "version": "2.0.0-next.36", + "resolved": "https://registry.npmjs.org/ariakit/-/ariakit-2.0.0-next.36.tgz", + "integrity": "sha512-H/ZqRgy5tGGKOcOsZ24lc5cBoQ83vgCFd+mC87UWdIEdYqhKNPPjFZona/V/l0SRtn9Mar+t93QbggyThOw6Qg==", + "requires": { + "@floating-ui/dom": "^1.0.0", + "ariakit-utils": "0.17.0-next.23" + } + }, + "ariakit-utils": { + "version": "0.17.0-next.23", + "resolved": "https://registry.npmjs.org/ariakit-utils/-/ariakit-utils-0.17.0-next.23.tgz", + "integrity": "sha512-r6a8rvjTBNbdNVWhGm4XL8hTlpIlP0G+yJf3No48kK6QpR1JN9QinLI/wMwWwJnxDYfwnBhkbcROCqF7iT/4ig==" + }, "colord": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/colord/-/colord-2.8.0.tgz", diff --git a/packages/components/package.json b/packages/components/package.json index e678c3a5514939..68115fe29233c0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -53,6 +53,7 @@ "@wordpress/primitives": "file:../primitives", "@wordpress/rich-text": "file:../rich-text", "@wordpress/warning": "file:../warning", + "ariakit": "2.0.0-next.36", "classnames": "^2.3.1", "colord": "^2.7.0", "dom-scroll-into-view": "^1.2.1", diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md b/packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md new file mode 100644 index 00000000000000..4a7e8ffaede437 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlArrow + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-arrow/component.tsx new file mode 100644 index 00000000000000..57dac95f1e1bb9 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/component.tsx @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectArrow } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlArrow } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlArrowProps } from '../types'; + +const UnforwardedSelectControlArrow = ( + props: WordPressComponentProps< SelectControlArrowProps, 'span', false >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlArrow( props ); + + // TODO: investigate incompatibility with the "as" prop. + return ; +}; + +// TODO: JSDocs +export const SelectControlArrow = forwardRef( UnforwardedSelectControlArrow ); + +export default SelectControlArrow; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-arrow/hook.ts new file mode 100644 index 00000000000000..436f30d03cc6bd --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/hook.ts @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlArrowProps } from '../types'; + +// TODO: +// - should we allow polymorphism ? +export const useSelectControlArrow = ( { + className, + ...props +}: WordPressComponentProps< SelectControlArrowProps, 'span', false > ) => { + const cx = useCx(); + const arrowClassName = useMemo( + () => cx( styles.arrow, className ), + [ className, cx ] + ); + + return { + ...props, + className: arrowClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-arrow/index.ts new file mode 100644 index 00000000000000..e53f4ca8179fbb --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlArrow } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md b/packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md new file mode 100644 index 00000000000000..4a019aac2f804f --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlGroupLabel + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-group-label/component.tsx new file mode 100644 index 00000000000000..f8b7e20bb9231a --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/component.tsx @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectGroupLabel } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlGroupLabel } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlGroupLabelProps } from '../types'; + +const UnforwardedSelectControlGroupLabel = ( + props: WordPressComponentProps< + SelectControlGroupLabelProps, + 'div', + false + >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlGroupLabel( props ); + + // TODO: investigate incompatibility with the "as" prop. + return ; +}; + +// TODO: JSDocs +export const SelectControlGroupLabel = forwardRef( + UnforwardedSelectControlGroupLabel +); + +export default SelectControlGroupLabel; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-group-label/hook.ts new file mode 100644 index 00000000000000..9bdb8bb675d579 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/hook.ts @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlGroupLabelProps } from '../types'; + +// TODO: +// - should we use 'option' instead of `div` for props inheritance? +// - should we allow polymorphism ? +export const useSelectControlGroupLabel = ( { + className, + ...props +}: WordPressComponentProps< SelectControlGroupLabelProps, 'div', false > ) => { + const cx = useCx(); + const groupLabelClassName = useMemo( + () => cx( styles.groupLabel, className ), + [ className, cx ] + ); + + return { + ...props, + className: groupLabelClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-group-label/index.ts new file mode 100644 index 00000000000000..1ac138c881dca4 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlGroupLabel } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/README.md b/packages/components/src/custom-select-control-new/custom-select-control-group/README.md new file mode 100644 index 00000000000000..fd210f9f95ce72 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlGroup + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-group/component.tsx new file mode 100644 index 00000000000000..b62994e3e4c709 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group/component.tsx @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectGroup } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlGroup } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlGroupProps } from '../types'; + +const UnforwardedSelectControlGroup = ( + props: WordPressComponentProps< SelectControlGroupProps, 'div', false >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlGroup( props ); + + // TODO: investigate incompatibility with the "as" prop. + return ; +}; + +// TODO: JSDocs +export const SelectControlGroup = forwardRef( UnforwardedSelectControlGroup ); + +export default SelectControlGroup; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-group/hook.ts new file mode 100644 index 00000000000000..4f406cfee2363e --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group/hook.ts @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlGroupProps } from '../types'; + +// TODO: +// - should we allow polymorphism ? +export const useSelectControlGroup = ( { + className, + ...props +}: WordPressComponentProps< SelectControlGroupProps, 'div', false > ) => { + const cx = useCx(); + const groupClassName = useMemo( + () => cx( styles.group, className ), + [ className, cx ] + ); + + return { + ...props, + className: groupClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-group/index.ts new file mode 100644 index 00000000000000..94cd33031a1e9f --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-group/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlGroup } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/README.md b/packages/components/src/custom-select-control-new/custom-select-control-item-check/README.md new file mode 100644 index 00000000000000..c24ffb32df44f2 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlItemCheck + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-item-check/component.tsx new file mode 100644 index 00000000000000..55c3c94efad95c --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/component.tsx @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectItemCheck } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlItemCheck } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlItemCheckProps } from '../types'; + +const UnforwardedSelectControlItemCheck = ( + props: WordPressComponentProps< + SelectControlItemCheckProps, + 'span', + false + >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlItemCheck( props ); + + return ; +}; + +// TODO: JSDocs +export const SelectControlItemCheck = forwardRef( + UnforwardedSelectControlItemCheck +); + +export default SelectControlItemCheck; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-item-check/hook.ts new file mode 100644 index 00000000000000..5bbbba0aa2883d --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/hook.ts @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlItemCheckProps } from '../types'; + +export const useSelectControlItemCheck = ( { + className, + ...props +}: WordPressComponentProps< SelectControlItemCheckProps, 'span', false > ) => { + const cx = useCx(); + const itemCheckClassName = useMemo( + () => cx( styles.itemCheck, className ), + [ className, cx ] + ); + + return { + ...props, + className: itemCheckClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-item-check/index.ts new file mode 100644 index 00000000000000..fd20b7feb64fb7 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlItemCheck } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/README.md b/packages/components/src/custom-select-control-new/custom-select-control-item/README.md new file mode 100644 index 00000000000000..f1b9345231bb11 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlItem + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-item/component.tsx new file mode 100644 index 00000000000000..432ce9703c9888 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item/component.tsx @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectItem } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlItem } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlItemProps } from '../types'; + +const UnforwardedSelectControlItem = ( + props: WordPressComponentProps< SelectControlItemProps, 'div', false >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlItem( props ); + + // TODO: investigate incompatibility with the "as" prop. + return ; +}; + +// TODO: JSDocs +export const SelectControlItem = forwardRef( UnforwardedSelectControlItem ); + +export default SelectControlItem; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-item/hook.ts new file mode 100644 index 00000000000000..fe64d2f85ce571 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item/hook.ts @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlItemProps } from '../types'; + +// TODO: +// - should we use 'option' instead of `div` for props inheritance? +// - should we allow polymorphism ? +export const useSelectControlItem = ( { + className, + ...props +}: WordPressComponentProps< SelectControlItemProps, 'div', false > ) => { + const cx = useCx(); + const itemClassName = useMemo( + () => cx( styles.item, className ), + [ className, cx ] + ); + + return { + ...props, + className: itemClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-item/index.ts new file mode 100644 index 00000000000000..42c91c3acd1c07 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-item/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlItem } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/README.md b/packages/components/src/custom-select-control-new/custom-select-control-row/README.md new file mode 100644 index 00000000000000..4d70ab39fa0e60 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-row/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlRow + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-row/component.tsx new file mode 100644 index 00000000000000..d4c2081ca3680c --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-row/component.tsx @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectRow } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlRow } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlRowProps } from '../types'; + +const UnforwardedSelectControlRow = ( + props: WordPressComponentProps< SelectControlRowProps, 'div', false >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlRow( props ); + + return ; +}; + +// TODO: JSDocs +export const SelectControlRow = forwardRef( UnforwardedSelectControlRow ); + +export default SelectControlRow; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-row/hook.ts new file mode 100644 index 00000000000000..9dbcec2b562476 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-row/hook.ts @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlRowProps } from '../types'; + +export const useSelectControlRow = ( { + className, + ...props +}: WordPressComponentProps< SelectControlRowProps, 'div', false > ) => { + const cx = useCx(); + const rowClassName = useMemo( + () => cx( styles.row, className ), + [ className, cx ] + ); + + return { + ...props, + className: rowClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-row/index.ts new file mode 100644 index 00000000000000..9171498f9b008d --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-row/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlRow } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/README.md b/packages/components/src/custom-select-control-new/custom-select-control-separator/README.md new file mode 100644 index 00000000000000..e3ca90cc5480bb --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/README.md @@ -0,0 +1,3 @@ +# CustomSelectControlSeparator + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-separator/component.tsx new file mode 100644 index 00000000000000..179d10dbac6452 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/component.tsx @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { SelectSeparator } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControlSeparator } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import type { SelectControlSeparatorProps } from '../types'; + +const UnforwardedSelectControlSeparator = ( + props: WordPressComponentProps< SelectControlSeparatorProps, 'hr', false >, + forwardedRef: ForwardedRef< any > +) => { + const allProps = useSelectControlSeparator( props ); + + return ; +}; + +// TODO: JSDocs +export const SelectControlSeparator = forwardRef( + UnforwardedSelectControlSeparator +); + +export default SelectControlSeparator; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-separator/hook.ts new file mode 100644 index 00000000000000..49220721da7545 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/hook.ts @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlSeparatorProps } from '../types'; + +export const useSelectControlSeparator = ( { + className, + ...props +}: WordPressComponentProps< SelectControlSeparatorProps, 'hr', false > ) => { + const cx = useCx(); + const separatorClassName = useMemo( + () => cx( styles.separator, className ), + [ className, cx ] + ); + + return { + ...props, + className: separatorClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-separator/index.ts new file mode 100644 index 00000000000000..f4610cf6cb8d68 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControlSeparator } from './hook'; diff --git a/packages/components/src/custom-select-control-new/custom-select-control/README.md b/packages/components/src/custom-select-control-new/custom-select-control/README.md new file mode 100644 index 00000000000000..54862525d4f901 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control/README.md @@ -0,0 +1,3 @@ +# CustomSelectControl + +TODO diff --git a/packages/components/src/custom-select-control-new/custom-select-control/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control/component.tsx new file mode 100644 index 00000000000000..ae336845d7fcc1 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control/component.tsx @@ -0,0 +1,101 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; +import { Select, SelectLabel, SelectPopover } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSelectControl } from './hook'; +import type { WordPressComponentProps } from '../../ui/context'; +import { CustomSelectControlItem, CustomSelectControlArrow } from '../'; +import type { SelectControlOption, SelectControlProps } from '../types'; + +const CustomDisplayedValue = ( { + options, + value, +}: { + options: SelectControlOption[]; + value?: string; +} ) => ( + <> + { /* Use the label associated to the option's value, fallback to the value itself */ } + { options.find( ( option ) => option.value === value )?.label ?? value } + + +); + +const UnforwardedSelectControl = ( + props: WordPressComponentProps< SelectControlProps, 'select', false >, + forwardedRef: ForwardedRef< any > +) => { + const { + label, + options, + children, + selectState, + wrapperClassName, + labelClassName, + selectClassName, + popoverClassName, + } = useSelectControl( props ); + + return ( +
+ + { label } + + + + { /* Popover arrow? */ } + { children ?? + options?.map( ( option, index ) => { + const key = + option.id || + `${ option.label }-${ option.value }-${ index }`; + return ( + + { option.label } + + ); + } ) } + +
+ ); +}; + +// TODO: JSDocs +export const SelectControl = forwardRef( UnforwardedSelectControl ); + +export default SelectControl; diff --git a/packages/components/src/custom-select-control-new/custom-select-control/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control/hook.ts new file mode 100644 index 00000000000000..8a195051c4db8f --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control/hook.ts @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import { useSelectState } from 'ariakit/select'; + +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import type { WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; +import type { SelectControlProps } from '../types'; + +// TODO: +// - should we use 'select' instead of `div` for props inheritance? +// - should we allow polymorphism ? +export const useSelectControl = ( { + value, + className, + ...props +}: WordPressComponentProps< SelectControlProps, 'select', false > ) => { + // TODO: take some of these settings as props? + const selectState = useSelectState( { + // TODO: check if it works, understand if we should expose + // a different prop for the initial value? + defaultValue: value, + sameWidth: true, + gutter: 4, + } ); + + // TODO: deprecate options prop + + const cx = useCx(); + const wrapperClassName = useMemo( + () => cx( styles.wrapper, className ), + [ className, cx ] + ); + const labelClassName = useMemo( + () => cx( styles.label, className ), + [ className, cx ] + ); + const selectClassName = useMemo( + () => cx( styles.select, className ), + [ className, cx ] + ); + const popoverClassName = useMemo( + () => cx( styles.popover, className ), + [ className, cx ] + ); + + return { + ...props, + selectState, + wrapperClassName, + labelClassName, + selectClassName, + popoverClassName, + }; +}; diff --git a/packages/components/src/custom-select-control-new/custom-select-control/index.ts b/packages/components/src/custom-select-control-new/custom-select-control/index.ts new file mode 100644 index 00000000000000..b4aa7c202c1766 --- /dev/null +++ b/packages/components/src/custom-select-control-new/custom-select-control/index.ts @@ -0,0 +1,2 @@ +export { default } from './component'; +export { useSelectControl } from './hook'; diff --git a/packages/components/src/custom-select-control-new/index.ts b/packages/components/src/custom-select-control-new/index.ts new file mode 100644 index 00000000000000..9991a9c298f82e --- /dev/null +++ b/packages/components/src/custom-select-control-new/index.ts @@ -0,0 +1,8 @@ +export { default as CustomSelectControl } from './custom-select-control'; +export { default as CustomSelectControlItem } from './custom-select-control-item'; +export { default as CustomSelectControlGroup } from './custom-select-control-group'; +export { default as CustomSelectControlGroupLabel } from './custom-select-control-group-label'; +export { default as CustomSelectControlArrow } from './custom-select-control-arrow'; +export { default as CustomSelectControlSeparator } from './custom-select-control-separator'; +export { default as CustomSelectControlRow } from './custom-select-control-row'; +export { default as CustomSelectControlItemCheck } from './custom-select-control-item-check'; diff --git a/packages/components/src/custom-select-control-new/stories/index.tsx b/packages/components/src/custom-select-control-new/stories/index.tsx new file mode 100644 index 00000000000000..3f3b803e4b7af5 --- /dev/null +++ b/packages/components/src/custom-select-control-new/stories/index.tsx @@ -0,0 +1,106 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * WordPress dependencies + */ +// import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { + CustomSelectControl, + CustomSelectControlItem, + CustomSelectControlGroup, + CustomSelectControlGroupLabel, + CustomSelectControlSeparator, +} from '../'; + +const meta: ComponentMeta< typeof CustomSelectControl > = { + component: CustomSelectControl, + title: 'Components (Experimental)/CustomSelectControlNew', + argTypes: {}, + parameters: { + controls: { + expanded: true, + }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +// TODO: +// - with `options` prop +// - controlled vs uncontrolled +// - with HTML `` (and related vanilla elements)? +// - example with custom author dropdown + +export const Default: ComponentStory< typeof CustomSelectControl > = ( + args +) => ( + + + + { /* Custom item label */ } + Planet Earth + + + + + +); +Default.args = { + label: 'Choose a planet', +}; + +export const WithGroupsAndSeparators: ComponentStory< + typeof CustomSelectControl +> = ( args ) => { + return ( + + { [ + { label: 'Primary', values: [ 'Red', 'Yellow', 'Blue' ] }, + { label: 'Secondary', values: [ 'Orange', 'Green', 'Purple' ] }, + { + label: 'Tertiary', + values: [ + 'Amber', + 'Vermilion', + 'Magenta', + 'Violet', + 'Teal', + 'Chartreuse', + ], + }, + ].map( ( { label, values }, groupIndex, groupArray ) => ( + <> + + + { label } + + { values.map( ( value, valueIndex ) => ( + + ) ) } + + { groupIndex < groupArray.length - 1 ? ( + + ) : null } + + ) ) } + + ); +}; +WithGroupsAndSeparators.args = { + label: 'Pick a color', +}; diff --git a/packages/components/src/custom-select-control-new/styles.ts b/packages/components/src/custom-select-control-new/styles.ts new file mode 100644 index 00000000000000..b90ba6fa08dca5 --- /dev/null +++ b/packages/components/src/custom-select-control-new/styles.ts @@ -0,0 +1,130 @@ +/** + * External dependencies + */ +import { css } from '@emotion/react'; + +/** + * Internal dependencies + */ + +const focused = css` + outline: 2px solid hsl( 204, 100%, 40% ); +`; + +// TODO: convert to Flex or HStack +export const wrapper = css` + display: flex; + flex-direction: column; + gap: 0.5rem; +`; + +// TODO: use base control label? Text? Heading? +export const label = css``; + +// TODO: convert to Flex? +export const select = css` + /* Should be set by parent */ + width: 200px; + display: flex; + height: 2.5rem; + cursor: default; + align-items: center; + justify-content: space-between; + gap: 0.25rem; + white-space: nowrap; + border-radius: 0.5rem; + background-color: hsl( 204, 20%, 94% ); + padding-left: 1rem; + padding-right: 1rem; + font-size: 1rem; + line-height: 1.5rem; + + &:hover { + background-color: hsl( 204, 20%, 91% ); + } + + &[aria-disabled='true'] { + opacity: 0.5; + } + + &:focus-visible, + &[data-focus-visible] { + ${ focused }; + } +`; + +// TODO: convert to Flex? +export const popover = css` + /* TODO: add flexibility, e.g.: min(var(--popover-available-height, 300px), 300px); */ + max-height: 20rem; + z-index: 50; + display: flex; + flex-direction: column; + overflow: auto; + overscroll-behavior: contain; + border-radius: 0.5rem; + border-width: 1px; + border-style: solid; + border-color: hsl( 204, 20%, 88% ); + background-color: hsl( 204, 20%, 100% ); + padding: 0.5rem; + color: hsl( 204, 10%, 10% ); + filter: drop-shadow( 0 4px 6px rgba( 0, 0, 0, 15% ) ); + + &:focus-visible, + &[data-focus-visible] { + ${ focused }; + } +`; + +export const item = css` + outline: none !important; + display: flex; + cursor: default; + scroll-margin: 0.5rem; + align-items: center; + gap: 0.5rem; + border-radius: 0.25rem; + padding: 0.5rem; + + &[data-active-item] { + background-color: hsl( 204, 100%, 40% ); + color: hsl( 0, 0%, 100% ); + } + + &[aria-disabled='true'] { + opacity: 0.5; + } +`; + +export const itemCheck = css` + /* TODO: styles (decide after prepping a storybook example) */ +`; + +export const arrow = css``; + +export const group = css``; + +export const groupLabel = css` + cursor: default; + padding: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + opacity: 0.6; +`; + +// TODO: convert to Flex or HStack +export const row = css` + display: 'flex'; +`; + +export const separator = css` + border-color: currentcolor; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + height: 0px; + border-top-width: 1px; + opacity: 0.25; + width: 100%; +`; diff --git a/packages/components/src/custom-select-control-new/types.ts b/packages/components/src/custom-select-control-new/types.ts new file mode 100644 index 00000000000000..1b1efefaf061d4 --- /dev/null +++ b/packages/components/src/custom-select-control-new/types.ts @@ -0,0 +1,70 @@ +/** + * External dependencies + */ +import type { ReactNode } from 'react'; + +type SelectControlOptionBase = { + value: string; + label?: string; + disabled?: boolean; +}; + +// options[] data object +export type SelectControlOption = SelectControlOptionBase & { + id?: string; +}; + +// React component props +export type SelectControlItemProps = SelectControlOptionBase & { + // Is classname necessary, with WordPressComponentProps? + className?: string; + children?: ReactNode; + // Do we want to expose this prop? + checked?: boolean; + // Should we expose this? + preventScrollOnKeyDown?: boolean; +}; + +export type SelectControlItemCheckProps = SelectControlOptionBase & { + // Is classname necessary, with WordPressComponentProps? + className?: string; + children?: ReactNode; +}; + +export type SelectControlProps = { + value?: string; + label?: string; + // TODO: explain that they are ignored if `children` is specified + options?: SelectControlOption[]; + children?: ReactNode; + // Is classname necessary, with WordPressComponentProps? + className?: string; +}; + +export type SelectControlArrowProps = { + // Is classname necessary, with WordPressComponentProps? + className?: string; +}; + +export type SelectControlGroupProps = { + children: ReactNode; + // Is classname necessary, with WordPressComponentProps? + className?: string; +}; + +export type SelectControlGroupLabelProps = { + children: ReactNode; + // Is classname necessary, with WordPressComponentProps? + className?: string; +}; + +export type SelectControlSeparatorProps = { + // Is classname necessary, with WordPressComponentProps? + className?: string; +}; + +export type SelectControlRowProps = { + children: ReactNode; + // Is classname necessary, with WordPressComponentProps? + className?: string; +}; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index cc5f7e3be2634b..32167c48f423ef 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -46,6 +46,7 @@ "src/color-palette/**/*", "src/color-picker/**/*", "src/confirm-dialog/**/*", + "src/custom-select-control-new/**/*", "src/dashicon/**/*", "src/date-time/**/*", "src/disabled/**/*",