From ad8020434ff85de6f64ae9be703633d90909c0a2 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 4 Jan 2022 19:31:57 +1000 Subject: [PATCH 01/31] Add BorderControl component This provides a new component that allows configuration of border color, style, and width. --- docs/manifest.json | 6 + .../border-control-dropdown/component.tsx | 139 ++++++++ .../border-control-dropdown/hook.ts | 92 +++++ .../border-control-dropdown/index.ts | 1 + .../border-control-style-picker/component.tsx | 86 +++++ .../border-control-style-picker/hook.ts | 34 ++ .../border-control-style-picker/index.ts | 1 + .../border-control/border-control/README.md | 159 +++++++++ .../border-control/component.tsx | 108 ++++++ .../src/border-control/border-control/hook.ts | 141 ++++++++ .../border-control/border-control/index.ts | 2 + .../components/src/border-control/index.ts | 2 + .../src/border-control/stories/index.js | 70 ++++ .../components/src/border-control/styles.ts | 158 ++++++++ .../src/border-control/test/index.js | 336 ++++++++++++++++++ .../components/src/border-control/types.ts | 137 +++++++ packages/components/src/index.js | 1 + packages/components/tsconfig.json | 1 + 18 files changed, 1474 insertions(+) create mode 100644 packages/components/src/border-control/border-control-dropdown/component.tsx create mode 100644 packages/components/src/border-control/border-control-dropdown/hook.ts create mode 100644 packages/components/src/border-control/border-control-dropdown/index.ts create mode 100644 packages/components/src/border-control/border-control-style-picker/component.tsx create mode 100644 packages/components/src/border-control/border-control-style-picker/hook.ts create mode 100644 packages/components/src/border-control/border-control-style-picker/index.ts create mode 100644 packages/components/src/border-control/border-control/README.md create mode 100644 packages/components/src/border-control/border-control/component.tsx create mode 100644 packages/components/src/border-control/border-control/hook.ts create mode 100644 packages/components/src/border-control/border-control/index.ts create mode 100644 packages/components/src/border-control/index.ts create mode 100644 packages/components/src/border-control/stories/index.js create mode 100644 packages/components/src/border-control/styles.ts create mode 100644 packages/components/src/border-control/test/index.js create mode 100644 packages/components/src/border-control/types.ts diff --git a/docs/manifest.json b/docs/manifest.json index cba23e20f0a07..cb10d3c6e80a3 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -611,6 +611,12 @@ "markdown_source": "../packages/components/src/base-field/README.md", "parent": "components" }, + { + "title": "BorderControl", + "slug": "border-control", + "markdown_source": "../packages/components/src/border-control/border-control/README.md", + "parent": "components" + }, { "title": "BoxControl", "slug": "box-control", diff --git a/packages/components/src/border-control/border-control-dropdown/component.tsx b/packages/components/src/border-control/border-control-dropdown/component.tsx new file mode 100644 index 0000000000000..b89ef1ef8daca --- /dev/null +++ b/packages/components/src/border-control/border-control-dropdown/component.tsx @@ -0,0 +1,139 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { closeSmall } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import BorderControlStylePicker from '../border-control-style-picker'; +import Button from '../../button'; +// @ts-ignore +import ColorIndicator from '../../color-indicator'; +// @ts-ignore +import ColorPalette from '../../color-palette'; +import Dropdown from '../../dropdown'; +import { HStack } from '../../h-stack'; +import { VStack } from '../../v-stack'; +import { contextConnect, WordPressComponentProps } from '../../ui/context'; +import { useBorderControlDropdown } from './hook'; +import { StyledLabel } from '../../base-control/styles/base-control-styles'; + +import type { DropdownProps, PopoverProps } from '../types'; +const noop = () => undefined; + +const BorderControlDropdown = ( + props: WordPressComponentProps< DropdownProps, 'div' >, + forwardedRef: React.Ref< any > +) => { + const { + __experimentalHasMultipleOrigins, + __experimentalIsRenderedInSidebar, + border, + colors, + disableCustomColors, + enableAlpha, + indicatorClassName, + onReset, + onColorChange, + onStyleChange, + popoverClassName, + popoverContentClassName, + popoverControlsClassName, + resetButtonClassName, + enableStyle = true, + ...otherProps + } = useBorderControlDropdown( props ); + + const { color, style } = border || {}; + const fallbackColor = !! style && style !== 'none' ? '#ddd' : undefined; + const indicatorBorderStyles = { + // The border style is set to `none` when border width is zero. Forcing + // the solid style in this case maintains the positioning of the inner + // ColorIndicator. + borderStyle: style === 'none' ? 'solid' : style, + // If there is no color selected but we have a style to display, apply + // a border color anyway. + borderColor: color || fallbackColor, + }; + + const renderToggle = ( { onToggle = noop } ) => ( + + ); + + const renderContent = ( { onClose }: PopoverProps ) => ( + <> + + + { __( 'Border color' ) } + + + ); + + return ( + + ); +}; + +const ConnectedBorderControlDropdown = contextConnect( + BorderControlDropdown, + 'BorderControlDropdown' +); + +export default ConnectedBorderControlDropdown; diff --git a/packages/components/src/border-control/border-control-dropdown/hook.ts b/packages/components/src/border-control/border-control-dropdown/hook.ts new file mode 100644 index 0000000000000..84035e4794618 --- /dev/null +++ b/packages/components/src/border-control/border-control-dropdown/hook.ts @@ -0,0 +1,92 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as styles from '../styles'; +import { parseUnit } from '../../unit-control/utils'; +import { useContextSystem, WordPressComponentProps } from '../../ui/context'; +import { useCx } from '../../utils/hooks/use-cx'; + +import type { DropdownProps } from '../types'; + +export function useBorderControlDropdown( + props: WordPressComponentProps< DropdownProps, 'div' > +) { + const { + border, + className, + colors, + onChange, + previousStyleSelection, + ...otherProps + } = useContextSystem( props, 'BorderControlDropdown' ); + + const [ widthValue ] = parseUnit( border?.width ); + const hasZeroWidth = widthValue === 0; + + const onColorChange = ( color?: string ) => { + const style = + border?.style === 'none' ? previousStyleSelection : border?.style; + const width = hasZeroWidth && !! color ? '1px' : border?.width; + + onChange( { color, style, width } ); + }; + + const onStyleChange = ( style?: string ) => { + const width = hasZeroWidth && !! style ? '1px' : border?.width; + onChange( { ...border, style, width } ); + }; + + const onReset = () => { + onChange( { + ...border, + color: undefined, + style: undefined, + } ); + }; + + // Generate class names. + const cx = useCx(); + const classes = useMemo( () => { + return cx( styles.BorderControlDropdown, className ); + }, [ className ] ); + + const indicatorClassName = useMemo( () => { + return cx( styles.BorderColorIndicator ); + }, [] ); + + const popoverClassName = useMemo( () => { + return cx( styles.BorderControlPopover ); + }, [] ); + + const popoverControlsClassName = useMemo( () => { + return cx( styles.BorderControlPopoverControls ); + }, [] ); + + const popoverContentClassName = useMemo( () => { + return cx( styles.BorderControlPopoverContent ); + }, [] ); + + const resetButtonClassName = useMemo( () => { + return cx( styles.ResetButton ); + }, [] ); + + return { + ...otherProps, + border, + className: classes, + colors, + indicatorClassName, + onColorChange, + onStyleChange, + onReset, + popoverClassName, + popoverContentClassName, + popoverControlsClassName, + resetButtonClassName, + }; +} diff --git a/packages/components/src/border-control/border-control-dropdown/index.ts b/packages/components/src/border-control/border-control-dropdown/index.ts new file mode 100644 index 0000000000000..b404d7fd44a81 --- /dev/null +++ b/packages/components/src/border-control/border-control-dropdown/index.ts @@ -0,0 +1 @@ +export { default } from './component'; diff --git a/packages/components/src/border-control/border-control-style-picker/component.tsx b/packages/components/src/border-control/border-control-style-picker/component.tsx new file mode 100644 index 0000000000000..6db2e6b4ed15a --- /dev/null +++ b/packages/components/src/border-control/border-control-style-picker/component.tsx @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import { lineDashed, lineDotted, lineSolid } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import { StyledLabel } from '../../base-control/styles/base-control-styles'; +import { View } from '../../view'; +import { VisuallyHidden } from '../../visually-hidden'; +import { contextConnect, WordPressComponentProps } from '../../ui/context'; +import { useBorderControlStylePicker } from './hook'; + +import type { LabelProps, StylePickerProps } from '../types'; + +const BORDER_STYLES = [ + { label: __( 'Solid' ), icon: lineSolid, value: 'solid' }, + { label: __( 'Dashed' ), icon: lineDashed, value: 'dashed' }, + { label: __( 'Dotted' ), icon: lineDotted, value: 'dotted' }, +]; + +const Label = ( props: LabelProps ) => { + const { label, hideLabelFromVision } = props; + + if ( ! label ) { + return null; + } + + return hideLabelFromVision ? ( + { label } + ) : ( + { label } + ); +}; + +const BorderControlStylePicker = ( + props: WordPressComponentProps< StylePickerProps, 'div' >, + forwardedRef: React.Ref< any > +) => { + const { + buttonClassName, + hideLabelFromVision, + label, + onChange, + value, + ...otherProps + } = useBorderControlStylePicker( props ); + + return ( + +