-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Borders: Add new BorderControl component #37769
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
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
ad80204
Add BorderControl component
aaronrobertshaw 08a4aaf
Improve docs and comments for width prop
aaronrobertshaw 45b2fd9
Target BackdropUI instead of div for removing border
aaronrobertshaw 3c072f3
Add RTL styles
aaronrobertshaw a6be837
Switch BorderControlStylePicker to use inner Flex component
aaronrobertshaw 95a74a7
Refactor label styles
aaronrobertshaw a7ecb60
Add cx to useMemo deps
aaronrobertshaw a3fd0e0
Use StyledField to target slider margin removal
aaronrobertshaw f6cbe7f
Fix type ignoring for color components
aaronrobertshaw 8d1c5a1
Move color indicator wrapper styles to dynamic class
aaronrobertshaw 51574d6
Use React.ForwardRef<any> for typing
aaronrobertshaw 3e23b82
Use Root to target UnitControl wrapper
aaronrobertshaw 757da54
Add default to docs for shouldSanitizeBorder
aaronrobertshaw c1f7f53
Add default to type docs for enableStyle
aaronrobertshaw 81bfad0
Update width type to CSSProperties['width']
aaronrobertshaw cf87f11
Add default for enableStyle to dropdown js docs
aaronrobertshaw 0b79b77
Remove extraneous width style
aaronrobertshaw 57d87ef
Fix unit and value parsing after changes to UnitControl utils
aaronrobertshaw 202800c
Make inner unit control fill available height
aaronrobertshaw dfe7470
Add tooltip for border style options
aaronrobertshaw 9be938d
Improve typing for colors array
aaronrobertshaw 1782b43
Improve aria labels for color and style popover toggle button
aaronrobertshaw b17da1a
Comment explaining color indicator styling choice
aaronrobertshaw b8da8d4
Only show border on indicator when style has been selected
aaronrobertshaw 9829605
Fix RTL handling and style naming
aaronrobertshaw 32e0120
Allow color and style picker to use flyout positioning
aaronrobertshaw 4f538d6
Add multi origin colors to storybook example
aaronrobertshaw 345b49a
Make dropdown header optional via new prop
aaronrobertshaw be99ab1
Update unit tests for optional color/style dropdown header
aaronrobertshaw 02562bb
Use more concise CSS rule to remove whitespace
aaronrobertshaw 535cd0d
Fix tooltips within color/style dropdown
aaronrobertshaw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.