-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Borders: Add new BorderControl component (#37769)
- Loading branch information
1 parent
c480389
commit ddbb8c3
Showing
18 changed files
with
1,816 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
252 changes: 252 additions & 0 deletions
252
packages/components/src/border-control/border-control-dropdown/component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
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; |
97 changes: 97 additions & 0 deletions
97
packages/components/src/border-control/border-control-dropdown/hook.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
} |
1 change: 1 addition & 0 deletions
1
packages/components/src/border-control/border-control-dropdown/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './component'; |
Oops, something went wrong.