-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
CSS Class Names: Add dropdown to choose from block styles #34521
Closed
Closed
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ab560bc
initial commit
ramonjd fccf670
Adding dispatch to update block styles
ramonjd 8fc814c
Formatting and adding a custom classname to control the icon containe…
ramonjd 8c74c85
Some light formatting/refactoring
ramonjd 24812b9
Update the menu heading to "Existing Styles."
shaunandrews 22981bb
Switching from flex to absolute positioning for the dropdown button. …
shaunandrews 32f9e98
This commit tests embedding the dropdown as a child of the TextContro…
ramonjd dc903fd
Extracting inspector controls component into a component.
ramonjd 202416c
Extracting dropdownmenu component
ramonjd de6dd44
TextControl:
ramonjd 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
155 changes: 155 additions & 0 deletions
155
packages/block-editor/src/components/custom-class-name-control/index.js
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,155 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import classnames from 'classnames'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
DropdownMenu, | ||
MenuGroup, | ||
MenuItem, | ||
TextControl, | ||
} from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { store as blocksStore } from '@wordpress/blocks'; | ||
import { useDispatch, useSelect } from '@wordpress/data'; | ||
import { check, moreVertical } from '@wordpress/icons'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { InspectorControls } from '../'; | ||
import { getActiveStyle, replaceActiveStyle } from '../block-styles/utils'; | ||
import { store as blockEditorStore } from '../../store'; | ||
|
||
/** | ||
* @typedef {Object} CustomClassNameMenuDropDownMenuProps | ||
* @property {string} activeStyle The currently active style. | ||
* @property {Object} blockStyles A collection of Block Styles. | ||
* @property {Function} onSelectStyleClassName An onClick handler. | ||
*/ | ||
|
||
/** | ||
* Returns a DropDownMenu component. | ||
* | ||
* @param {CustomClassNameMenuDropDownMenuProps} props The component. | ||
* @return {WPComponent} The menu item component. | ||
*/ | ||
function CustomClassNameMenuDropDownMenu( { | ||
activeStyle, | ||
blockStyles, | ||
onSelectStyleClassName, | ||
} ) { | ||
return ( | ||
<DropdownMenu | ||
className="additional-class-name-control__block-style-dropdown" | ||
icon={ moreVertical } | ||
label={ __( 'Existing Styles' ) } | ||
> | ||
{ ( { onClose } ) => ( | ||
<MenuGroup label={ __( 'Block style classes' ) }> | ||
{ blockStyles.map( ( style ) => { | ||
const isSelected = activeStyle?.name === style.name; | ||
const icon = isSelected ? check : null; | ||
return ( | ||
<MenuItem | ||
key={ style?.label } | ||
icon={ icon } | ||
isSelected={ isSelected } | ||
onClick={ () => { | ||
onSelectStyleClassName( style ); | ||
onClose(); | ||
} } | ||
role="menuitemcheckbox" | ||
> | ||
{ style?.label } | ||
</MenuItem> | ||
); | ||
} ) } | ||
</MenuGroup> | ||
) } | ||
</DropdownMenu> | ||
); | ||
} | ||
|
||
/** | ||
* @typedef {Object} CustomClassNameControlProps | ||
* @property {string} clientId Selected Block clientId. | ||
* @property {string} name Selected Block name. | ||
* @property {Object} attributes Selected Block's attributes. | ||
* @property {Function} setAttributes Set attributes callback. | ||
*/ | ||
|
||
/** | ||
* Control to display custom class name control dropdown and text input. | ||
* | ||
* @param {CustomClassNameControlProps} props Component props. | ||
* | ||
* @return {WPElement} Font appearance control. | ||
*/ | ||
export default function CustomClassNameControl( { | ||
clientId, | ||
name, | ||
attributes, | ||
setAttributes, | ||
} ) { | ||
const { updateBlockAttributes } = useDispatch( blockEditorStore ); | ||
|
||
const blockStyles = useSelect( | ||
( select ) => select( blocksStore ).getBlockStyles( name ), | ||
[ name, attributes.className ] | ||
); | ||
|
||
const hasBlockStyles = blockStyles && !! blockStyles.length; | ||
|
||
const activeStyle = hasBlockStyles | ||
? getActiveStyle( blockStyles, attributes.className || '' ) | ||
: null; | ||
|
||
const onSelectStyleClassName = ( style ) => { | ||
const styleClassName = replaceActiveStyle( | ||
attributes.className, | ||
activeStyle, | ||
style | ||
); | ||
updateBlockAttributes( clientId, { | ||
className: styleClassName, | ||
} ); | ||
}; | ||
|
||
const additionalClassNameContainerClasses = classnames( | ||
'additional-class-name-control__container', | ||
{ | ||
'has-block-styles': hasBlockStyles, | ||
} | ||
); | ||
|
||
return ( | ||
<InspectorControls __experimentalGroup="advanced"> | ||
<div className={ additionalClassNameContainerClasses }> | ||
<TextControl | ||
className="additional-class-name-control__text-control" | ||
autoComplete="off" | ||
label={ __( 'Additional CSS class(es)' ) } | ||
value={ attributes.className || '' } | ||
onChange={ ( nextValue ) => { | ||
setAttributes( { | ||
className: nextValue !== '' ? nextValue : undefined, | ||
} ); | ||
} } | ||
help={ __( 'Separate multiple classes with spaces.' ) } | ||
> | ||
{ hasBlockStyles && ( | ||
<CustomClassNameMenuDropDownMenu | ||
activeStyle={ activeStyle } | ||
blockStyles={ blockStyles } | ||
onSelectStyleClassName={ onSelectStyleClassName } | ||
/> | ||
) } | ||
</TextControl> | ||
</div> | ||
</InspectorControls> | ||
); | ||
} |
28 changes: 28 additions & 0 deletions
28
packages/block-editor/src/components/custom-class-name-control/style.scss
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,28 @@ | ||
.additional-class-name-control__container.has-block-styles { | ||
.additional-class-name-control__text-control > .components-base-control__field { | ||
position: relative; | ||
} | ||
} | ||
|
||
.additional-class-name-control__block-style-dropdown { | ||
position: absolute; | ||
top: auto; | ||
right: 2px; | ||
bottom: 2px; | ||
background: $white; | ||
|
||
button.components-button.has-icon { | ||
min-width: 24px; | ||
height: 24px; | ||
padding: 0; | ||
|
||
svg { | ||
height: 27px; | ||
width: 27px; | ||
@include break-medium() { | ||
height: 18px; | ||
width: 18px; | ||
} | ||
} | ||
} | ||
} |
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
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
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
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
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By way of explanation, rendering
children
here was a trade off.Following the design requirements, we need to be able to add a dropdown menu as a sibling of the text control input.
We could have created a custom component using the constituent parts of
<TextControl />
. The Advanced panel, however, uses<TextControl />
multiple times, which means that updates would have to be copied over to the custom component to retain consistency.Does adding
children
here contravene any component guidelines from a technical or philosophical standpoint?As far as I can tell, there are no side effects to adding and using the
children
prop, though I am worried that it might be "impure" in that it invites manifestations of a base component.I'm overthinking things, yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be grateful for your thoughts here @ciampo whenever you get time. 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the ping! Always happy to advise / give direction on components :)
I would say that rendering
children
inside aTextControl
goes a bit against the way the component is supposed to work.TextControl
, in my mind, is supposed to be an enrichedinput
field.I would personally recommend that, instead of adding
children
toTextInput
, you render the dropdown menu as a sibling ofTextInput
in theCustomClassNameControl
component.Also, cc'ing @diegohaz and @mirka in case they wanted to add their views here as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback and for confirming what the tiny voice in my brain was trying to tell me. I was truly struggling with the conflict 😆
I'll try that out, cheers! If the other Advanced control inputs end up looking or working differently I can try to switch them all over to TextInput as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I just realized that
TextInput
isn't a component yet. Or at least we haven't migrated it from g2.I should know, I started it and never finished! 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did initially look into it as well after you started it, but we later stopped because it was a very complicated task, since
TextInput
is very complex (it tries to handle single/multi line text and number input) and represents a very different approach to what we currently have in Gutenberg.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the background info. 🙇
I discovered personally how tricky it was 😬 so I feel better that folks with a lot more experience than I have also felt the same!