Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Borders: Add new BorderControl component #37769

Merged
merged 31 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ad80204
Add BorderControl component
aaronrobertshaw Jan 4, 2022
08a4aaf
Improve docs and comments for width prop
aaronrobertshaw Feb 18, 2022
45b2fd9
Target BackdropUI instead of div for removing border
aaronrobertshaw Feb 18, 2022
3c072f3
Add RTL styles
aaronrobertshaw Feb 18, 2022
a6be837
Switch BorderControlStylePicker to use inner Flex component
aaronrobertshaw Feb 18, 2022
95a74a7
Refactor label styles
aaronrobertshaw Feb 18, 2022
a7ecb60
Add cx to useMemo deps
aaronrobertshaw Feb 18, 2022
a3fd0e0
Use StyledField to target slider margin removal
aaronrobertshaw Feb 18, 2022
f6cbe7f
Fix type ignoring for color components
aaronrobertshaw Feb 18, 2022
8d1c5a1
Move color indicator wrapper styles to dynamic class
aaronrobertshaw Feb 18, 2022
51574d6
Use React.ForwardRef<any> for typing
aaronrobertshaw Feb 18, 2022
3e23b82
Use Root to target UnitControl wrapper
aaronrobertshaw Feb 18, 2022
757da54
Add default to docs for shouldSanitizeBorder
aaronrobertshaw Feb 18, 2022
c1f7f53
Add default to type docs for enableStyle
aaronrobertshaw Feb 18, 2022
81bfad0
Update width type to CSSProperties['width']
aaronrobertshaw Feb 18, 2022
cf87f11
Add default for enableStyle to dropdown js docs
aaronrobertshaw Feb 18, 2022
0b79b77
Remove extraneous width style
aaronrobertshaw Feb 18, 2022
57d87ef
Fix unit and value parsing after changes to UnitControl utils
aaronrobertshaw Mar 15, 2022
202800c
Make inner unit control fill available height
aaronrobertshaw Mar 15, 2022
dfe7470
Add tooltip for border style options
aaronrobertshaw Mar 16, 2022
9be938d
Improve typing for colors array
aaronrobertshaw Mar 16, 2022
1782b43
Improve aria labels for color and style popover toggle button
aaronrobertshaw Mar 16, 2022
b17da1a
Comment explaining color indicator styling choice
aaronrobertshaw Mar 16, 2022
b8da8d4
Only show border on indicator when style has been selected
aaronrobertshaw Mar 18, 2022
9829605
Fix RTL handling and style naming
aaronrobertshaw Mar 22, 2022
32e0120
Allow color and style picker to use flyout positioning
aaronrobertshaw Mar 22, 2022
4f538d6
Add multi origin colors to storybook example
aaronrobertshaw Mar 22, 2022
345b49a
Make dropdown header optional via new prop
aaronrobertshaw Mar 22, 2022
be99ab1
Update unit tests for optional color/style dropdown header
aaronrobertshaw Mar 22, 2022
02562bb
Use more concise CSS rule to remove whitespace
aaronrobertshaw Mar 23, 2022
535cd0d
Fix tooltips within color/style dropdown
aaronrobertshaw Mar 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/**
* External dependencies
*/
import type { CSSProperties } from 'react';

/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { closeSmall } from '@wordpress/icons';

/**
* Internal dependencies
*/
import BorderControlStylePicker from '../border-control-style-picker';
import Button from '../../button';
import ColorIndicator from '../../color-indicator';
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 {
Color,
ColorOrigin,
Colors,
DropdownProps,
PopoverProps,
} from '../types';

const noop = () => undefined;
const getColorObject = (
colorValue: CSSProperties[ 'borderColor' ],
colors: Colors | undefined,
hasMultipleColorOrigins: boolean
) => {
if ( ! colorValue || ! colors ) {
return;
}

if ( hasMultipleColorOrigins ) {
let matchedColor;

( colors as ColorOrigin[] ).some( ( origin ) =>
origin.colors.some( ( color ) => {
if ( color.color === colorValue ) {
matchedColor = color;
return true;
}

return false;
} )
);

return matchedColor;
}

return ( colors as Color[] ).find(
( color ) => color.color === colorValue
);
};

const getToggleAriaLabel = (
colorValue: CSSProperties[ 'borderColor' ],
colorObject: Color | undefined,
style: CSSProperties[ 'borderStyle' ],
isStyleEnabled: boolean
) => {
if ( isStyleEnabled ) {
if ( colorObject ) {
return style
? sprintf(
// translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:". %3$s: The current border style selection e.g. "solid".
'Border color and style picker. The currently selected color is called "%1$s" and has a value of "%2$s". The currently selected style is "%3$s".',
colorObject.name,
colorObject.color,
style
)
: sprintf(
// translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:".
'Border color and style picker. The currently selected color is called "%1$s" and has a value of "%2$s".',
colorObject.name,
colorObject.color
);
}

if ( colorValue ) {
return style
? sprintf(
// translators: %1$s: The color's hex code e.g.: "#f00:". %2$s: The current border style selection e.g. "solid".
'Border color and style picker. The currently selected color has a value of "%1$s". The currently selected style is "%2$s".',
colorValue,
style
)
: sprintf(
// translators: %1$s: The color's hex code e.g.: "#f00:".
'Border color and style picker. The currently selected color has a value of "%1$s".',
colorValue
);
}

return __( 'Border color and style picker.' );
}

if ( colorObject ) {
return sprintf(
// translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:".
'Border color picker. The currently selected color is called "%1$s" and has a value of "%2$s".',
colorObject.name,
colorObject.color
);
}

if ( colorValue ) {
return sprintf(
// translators: %1$s: The color's hex code e.g.: "#f00:".
'Border color picker. The currently selected color has a value of "%1$s".',
colorValue
);
}

return __( 'Border color picker.' );
};

const BorderControlDropdown = (
props: WordPressComponentProps< DropdownProps, 'div' >,
forwardedRef: React.ForwardedRef< any >
) => {
const {
__experimentalHasMultipleOrigins,
__experimentalIsRenderedInSidebar,
border,
colors,
disableCustomColors,
enableAlpha,
indicatorClassName,
indicatorWrapperClassName,
onReset,
onColorChange,
onStyleChange,
popoverClassName,
popoverContentClassName,
popoverControlsClassName,
resetButtonClassName,
showDropdownHeader,
enableStyle = true,
...otherProps
} = useBorderControlDropdown( props );

const { color, style } = border || {};
const colorObject = getColorObject(
color,
colors,
!! __experimentalHasMultipleOrigins
);

const toggleAriaLabel = getToggleAriaLabel(
color,
colorObject,
style,
enableStyle
);

const dropdownPosition = __experimentalIsRenderedInSidebar
? 'bottom left'
: undefined;

const renderToggle = ( { onToggle = noop } ) => (
<Button
onClick={ onToggle }
variant="tertiary"
aria-label={ toggleAriaLabel }
position={ dropdownPosition }
>
<span className={ indicatorWrapperClassName }>
<ColorIndicator
className={ indicatorClassName }
colorValue={ color }
/>
</span>
</Button>
);

const renderContent = ( { onClose }: PopoverProps ) => (
<>
<VStack className={ popoverControlsClassName } spacing={ 6 }>
{ showDropdownHeader ? (
<HStack>
<StyledLabel>{ __( 'Border color' ) }</StyledLabel>
<Button
isSmall
label={ __( 'Close border color' ) }
icon={ closeSmall }
onClick={ onClose }
/>
</HStack>
) : undefined }
<ColorPalette
mirka marked this conversation as resolved.
Show resolved Hide resolved
className={ popoverContentClassName }
value={ color }
onChange={ onColorChange }
{ ...{ colors, disableCustomColors } }
__experimentalHasMultipleOrigins={
__experimentalHasMultipleOrigins
}
__experimentalIsRenderedInSidebar={
__experimentalIsRenderedInSidebar
}
clearable={ false }
enableAlpha={ enableAlpha }
/>
{ enableStyle && (
<BorderControlStylePicker
label={ __( 'Style' ) }
value={ style }
onChange={ onStyleChange }
/>
) }
</VStack>
<Button
className={ resetButtonClassName }
variant="tertiary"
onClick={ () => {
onReset();
onClose();
} }
>
{ __( 'Reset to default' ) }
</Button>
</>
);

return (
<Dropdown
renderToggle={ renderToggle }
renderContent={ renderContent }
contentClassName={ popoverClassName }
{ ...otherProps }
ref={ forwardedRef }
/>
);
};

const ConnectedBorderControlDropdown = contextConnect(
BorderControlDropdown,
'BorderControlDropdown'
);

export default ConnectedBorderControlDropdown;
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import * as styles from '../styles';
import { parseQuantityAndUnitFromRawValue } 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 ] = parseQuantityAndUnitFromRawValue( 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, cx ] );

const indicatorClassName = useMemo( () => {
return cx( styles.borderColorIndicator );
}, [ cx ] );

const indicatorWrapperClassName = useMemo( () => {
return cx( styles.colorIndicatorWrapper( border ) );
}, [ border, cx ] );

const popoverClassName = useMemo( () => {
return cx( styles.borderControlPopover );
}, [ cx ] );

const popoverControlsClassName = useMemo( () => {
return cx( styles.borderControlPopoverControls );
}, [ cx ] );

const popoverContentClassName = useMemo( () => {
return cx( styles.borderControlPopoverContent );
}, [ cx ] );

const resetButtonClassName = useMemo( () => {
return cx( styles.resetButton );
}, [ cx ] );

return {
...otherProps,
border,
className: classes,
colors,
indicatorClassName,
indicatorWrapperClassName,
onColorChange,
onStyleChange,
onReset,
popoverClassName,
popoverContentClassName,
popoverControlsClassName,
resetButtonClassName,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './component';
Loading