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

Predefined set of font sizes #5269

Merged
merged 14 commits into from
Mar 20, 2018
Merged

Predefined set of font sizes #5269

merged 14 commits into from
Mar 20, 2018

Conversation

mtias
Copy link
Member

@mtias mtias commented Feb 26, 2018

Allow font-size control to have a predefined set of sizes (small, regular, large, larger) which applies classes instead of inline font-size style values. If the value does not correspond to a value in the supplied set, it falls back to previous mechanism.

Themes will be able to define their own set to overwrite the default.

The design aim would be to add a new component (segmented control) which we could use for the image size set as well.

Implementation

  • Introduce new ButtonGroup component.
  • Add notion of text class to paragraph block.
  • Define predefined set of font sizes.
  • If a font size matches a class, use class instead of inline style.

Current State

image

@mtias mtias added the [Status] In Progress Tracking issues with work in progress label Feb 26, 2018
@@ -81,6 +84,39 @@ class ParagraphBlock extends Component {
this.nodeRef = node;
}

setFontSize( value ) {
const { setAttributes } = this.props;
if ( fontSizeValues.includes( value ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array#includes is not polyfilled. This will error in IE11.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might try findKey and an object of font size values to simplify this:

const FONT_SIZES = {
	small: 14,
	// ...
};

// ...

setFontSize( fontSize ) {
	const size = findKey( FONT_SIZES, fontSize );
	let textClass;
	if ( size ) {
		textClass = `is-${ size }-text`;
	}

	this.props.setAttributes( { fontSize, textClass } );
}

const { setAttributes } = this.props;
if ( fontSizeValues.includes( value ) ) {
if ( fontSizeValues[ 0 ] === value ) {
setAttributes( { fontSize: fontSizeValues[ 0 ], textClass: 'is-small-text' } );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, there's redundancy between fontSize and textClass. I think we only need one or the other, probably the modifier "small", "regular", "large". Everything else (the class name, the pixel value) can be derived from the constant value.

@jasmussen
Copy link
Contributor

jasmussen commented Feb 27, 2018

I like the arbitrary font size slider, it allows for almost illustration-like layouts and designs, especially combined with colors. Although the slider can feel like a heavy-handed interface for adjusting things, in this particular case, I find it an enjoyable UI, because it can quickly cross a very wide range of sizes, and with the live preview on the left this can make for a pretty magical experience.

It feels like the challenge in this case is the confusion around inline styles and classes, and making it obvious when either is used. This is a difficult UI. While one solution could be to remove the slider in favor of an input field, this doesn't solve the particular issue of clarifying to the user when a value is set and customized vs. when the value is intrinsic or default.

The closest pattern to this, is images. Images work fine when their width and heights are unset — sometimes that's what you want. But other times you want to manually configure those dimensions. Matías you even mentioned the pill-button shortcut UI for the ticket to address that: #4914 (comment).

To see how to improve the UI, I decided to try and tackle both font size, and image dimensions, to see if the pattern could be replicated. As part of that I looked at some improvements to the slider design itself, also embracing the pill button shortcuts.

screen shot 2018-02-27 at 10 40 10

Notes:

  • There's an extra A icon, larger than the left one, to indicate the relationship between small and big sizes.
  • When the font size is undefined, the actual font size is shown as a placeholder, in this case 13. It reflects the size of the text, but also by being grayed, that this size isn't explicitly set.
  • In addition to showing the unset size, we show the slider handle in the correct position that would correspond to the default value also.
  • Pressing the Reset button will always return the slider to that position, and unset the number so the default value is shown again as a placeholder.
  • If you pull the slider, or type a number in the stepper, you set the font size to a non-default value.
  • Below the slider there are shortcuts in a pill button. These are simply just that, shortcuts to set the value above. Note how 24 is pressed, and value is input above, and the slider is also set.

This "shortcut" pattern, I tried replicating for images as well, where the intrinsic values for an image (when not sized) are shown as placeholder text, and a press of the shortcut button to 100% sets those values explicitly.

Thoughts? CC: @mtias


Sidenotes: I noticed that GoDaddy's site builder has a slider for font sizes also, though it's for the whole document:

godaddy sizer

If we feel the slider is being too widely used, we can eliminate it in places where the precision control isn't quite as valuable. Perhaps it's not the ideal interface for the gallery columns interface. Maybe that could be just a stepper, perhaps with a little CSS to make the up/down buttons a little bigger and friendlier.

@jasmussen
Copy link
Contributor

Here's a version that uses T-shirt size verbiage for the buttons. I think it works well, and this would translate better to themes providing their own values for each of those buttons:

screen shot 2018-02-28 at 09 09 26

@mtias
Copy link
Member Author

mtias commented Feb 28, 2018

What about placing the sizes at the top of the slider? I see the relationship as "basic -> advanced". Same as with colors.

@@ -118,20 +145,58 @@ class ParagraphBlock extends Component {
isSelected && (
<InspectorControls key="inspector">
<PanelBody title={ __( 'Text Settings' ) }>
<div className="blocks-font-size__main">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole thing should become a reusable block-component.

text-transform: uppercase;
font-style: normal;
p {
&.is-small-text {
Copy link
Member

@jorgefilipecosta jorgefilipecosta Mar 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this classes (is-small-text, is-regular-text) etc... be outside of paragraph so other blocks can make use of our font size mechanism?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe, but I'm cautious about defining styles generically for *.is-small-text. Also, in some other blocks reusing this component, they might want to have more fine-grained targets.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a problem because it is applying outside the paragraph block. There's no scoping on the p selector to make this specific to the paragraph block.

@mtias mtias added Design [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Feature] Blocks Overall functionality of blocks and removed [Status] In Progress Tracking issues with work in progress labels Mar 8, 2018
@@ -46,6 +49,13 @@ const ContrastCheckerWithFallbackStyles = withFallbackStyles( ( node, ownProps )
};
} )( ContrastChecker );

const FONT_SIZES = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we try to read this values from the theme, e.g: provide and extensibility mechanism for themes to change them, or use invisible elements plus getComputedStyles to check if themes css changes our styles?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely allow a theme to set this, but should be done separately

max={ 100 }
beforeIcon="editor-textcolor"
/>
<span>{ attributes.textClass }</span>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just for debugging

@@ -242,6 +307,9 @@ const schema = {
fontSize: {
type: 'number',
},
textClass: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have two attributes for font size (fontSize and textClass) both holding information about the size of the text. Plus having a class as attribute seems redundant because it is already in the p tag and it seems it should have been parsed from there.
What about using just one attribute with for the size and when it is a number it is interpreted that a specific font size should be used, when is something like %regular, we assume that a class should be used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and it seems it should have been parsed from there.

I agree.

What about using just one attribute with for the size and when it is a number it is interpreted that a specific font size should be used, when is something like %regular, we assume that a class should be used?

Can you expand a bit on how you see this implemented?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not certain if it is the test approach or not, but we can have just the fontSize attribute as a string. On save/edit if it starts with '%' like '%regular' we assume it's a "named font size" / class and use it to generate the corresponding class e.g: "is-regular-text". If the fontSize is a number we do the same as we do now.
I used a version of this logic in #5273.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the obscure prefix?

isFinite( '26' )
// true
isFinite( 'regular' )
// false

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we don't need any prefix in this case, in the colors one we needed and I did not made the mental switch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even in the color one, can't you identify a color value? What is it, a hex?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the colors, a color can be 'red', '#ff0', or 'rgb( 255, 0, 0)' and we may want a named color called red but with a value equal to #d82e00 so a special prefix for our named colors seems like the best approach e.g: %red.

Copy link
Member

@aduth aduth Mar 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasmussen
Copy link
Contributor

This is working EXCEPTIONALLY well.

I have only ONE tiny niggle, which is best illustrated in this GIF:

textsettings

At the end when I press Reset, the slider handle is centered in the slider. Which, in context of just having resized the text, makes it seem like suddenly the text should be large (because of the slider handle position).

I realize that the handle at the center is the "unset" configuration for the element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range — but is there something we can do to indicate that the slider is unset?

@mtias
Copy link
Member Author

mtias commented Mar 8, 2018

I realize that the handle at the center is the "unset" configuration for the element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range — but is there something we can do to indicate that the slider is unset?

We could try hiding the disc. Once you click on the bar, it would set it again.

@jasmussen
Copy link
Contributor

Or making it the same gray as the track maybe. Or maybe it's a non issue... You'll learn what reset means by the context anyway.

text-transform: uppercase;
font-style: normal;
p {
&.is-small-text {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a problem because it is applying outside the paragraph block. There's no scoping on the p selector to make this specific to the paragraph block.

<PanelBody title={ __( 'Text Settings' ) }>
<PanelBody title={ __( 'Text Settings' ) } className="blocks-font-size">
<div className="blocks-font-size__main">
<ButtonGroup aria-label={ __( 'Font Size' ) }>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't we want aria-label on each of the button options instead for "Small", "Medium", etc. ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this and opted for not having one for now.

@@ -242,6 +307,9 @@ const schema = {
fontSize: {
type: 'number',
},
textClass: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the obscure prefix?

isFinite( '26' )
// true
isFinite( 'regular' )
// false

const classes = classnames( 'components-button-group', className );

return (
<div { ...props } className={ classes } role="group" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A group should be used to form a logical collection of items with related functionality, such as children in a tree widget forming a collection of siblings in a hierarchy, or a collection of items having the same container in a directory. However, when a group is used in the context of list, authors must limit its children to listitem elements. Group elements may be nested.

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_group_role

Maybe we need to Children.map to apply a wrapper to each child?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in slack channel and markup seemed good as is.

aria-pressed={ attributes.textClass === 'is-small-text' }
onClick={ () => this.setFontSize( FONT_SIZES.small ) }
>
S
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How well do these single-characters localize?

<PanelBody title={ __( 'Text Settings' ) } className="blocks-font-size">
<div className="blocks-font-size__main">
<ButtonGroup aria-label={ __( 'Font Size' ) }>
<Button
Copy link
Member

@aduth aduth Mar 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map( {
	S: 'small',
	M: 'regular',
	L: 'large',
	XL: 'larger',
}, ( size, label ) => (
	<Button
		key={ size }
		isLarge
		isPrimary={ attributes.textClass === `is-${ size }-text` }
		aria-pressed={ attributes.textClass === `is-${ size }-text` }
		onClick={ () => this.setFontSize( FONT_SIZES[ size ] ) }
	>
		{ label }
	</Button>
) )

@@ -117,21 +144,63 @@ class ParagraphBlock extends Component {
),
isSelected && (
<InspectorControls key="inspector">
<PanelBody title={ __( 'Text Settings' ) }>
<PanelBody title={ __( 'Text Settings' ) } className="blocks-font-size">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect we're planning to extract this to a separate component?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed.

@mtias mtias force-pushed the add/predefined-font-sizes branch from ce8d4a9 to 3959a97 Compare March 19, 2018 13:23
@@ -1,3 +1,13 @@
.editor-block-list__block:not( .is-multi-selected ) .wp-block-paragraph {
background: white;
}

.blocks-font-size__main {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be extracted into a block component.

fontSize,
} = attributes;

const thresholdFontSize = findKey( FONT_SIZES, ( size ) => size === fontSize );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing here is that we'd want this to be abstracted away from blocks when we make FontSize a component.

Copy link
Contributor

@youknowriad youknowriad Mar 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be a filterLike function props, fontSize => updateProps

const props = addFontSizeProps( { style, className }, fontSize )

const className = classnames( {
[ `align${ width }` ]: width,
'has-background': backgroundColor,
'has-drop-cap': dropCap,
} );
}, thresholdFontSize ? `is-${ thresholdFontSize }-text` : undefined );
Copy link
Member

@aduth aduth Mar 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not assign into the prior object similar to as align is assigned?

classnames( {
	// ...
	[ `is-${ thresholdFontSize }-text` ]: thresholdFontSize
} );

}

&.has-drop-cap {
&:first-letter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nesting necessary? vs.

&.has-drop-cap:first-letter {

}

const styles = {
backgroundColor: backgroundColor,
color: textColor,
fontSize: fontSize,
fontSize: thresholdFontSize ? null : fontSize,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally in this situation, I would use thresholdFontSize ? undefined : fontSize,. But in this case, I think using null is safe as false values will be ignored.

fontSize,
} = attributes;

const thresholdFontSize = findKey( FONT_SIZES, ( size ) => size === fontSize );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attribute contains the font size as a number. I think this raises some problems. If I'm in a theme that sets large font size as 36 and I switch to a theme that sets large fontSize as 37 the blocks with large fontSize created on the old theme will become invalid.
I think for threshold font sizes we need to store the attribute as a string and not refer to the current number.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is why I had a separate attribute for the class. The number should only be used when it's a custom value done by the user (and internally to render the inline style). But we should not save anything about the number if it's coming from the predefined size.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware of the need to make these thresholds customizable. In that case, I can update the PR to restore the size attribute

if ( fontSizeValue ) {
return fontSizeValue;
}
const fontSizeNumeric = parseInt( fontSize ); //compatibility with old font size attribute.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not certain if this back compatibility logic should exist. But without it, although the existing blocks are not changed, in the front-end or markup. The editor does not show existing blocks with custom sizes with the correct size (but does not changes them so they continue to be ok in the frontEnd).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this could be achieved with the "deprecated" block logic using the migrate function https://wordpress.org/gutenberg/handbook/block-api/deprecated-blocks/#changing-the-attributes-set

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep it for a few releases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi I think we can add a migration function as @youknowriad suggested to convert fontSize to customFontSize if the font size is a number, I will give it a try.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with not doing this conversion and just dropping the logic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @mtias I was in the middle of adding the conversion logic and did not check your message.
The conversion logic seems to be working fine now and correctly converts exist fontSize to customFontSize, using our deprecation mechanism, as the type is different before it was number now it is string I think having this logic is fine and does not cause problems with the new paragraph blocks,.
I did some tests and it looks like things are working well.

@youknowriad
Copy link
Contributor

After taking a look more deeply to the latest changes, I noticed an issue:

An old paragraph with a custom font size is still valid even if the markup is different.
The reason it’s still valid is tricky. Because if you take a look at the generated markup it’s different than the saved one (the font-size style is missing from the new block). But the block is not marked as invalid because we have a deprecated version that doesn’t care about the content (the one that handles p-less paragraphs)

So I think we have two options:

  • Drop the custom logic to handle old fontSize values in getFontSize because it makes you think the font size is customized but it's not really in the frontend.

  • Add a new deprecated version for the paragraph block to migrate the old fontSize to customFontSize.

@jorgefilipecosta
Copy link
Member

Hi @youknowriad, this PR now uses the deprecation mechanism to convert existing blocks. The problem you described should be fixed.

@youknowriad
Copy link
Contributor

I'll defer to @mtias here. While this fixes the problem, it seems that the default block's implementation is getting bigger and bigger just to handle legacy issues. While that's totally fine once Gutenberg merged into Core, it's good to try to limit it while not done yet.

Maybe it's fine to keep it for a release or two and then remove it later.

@jorgefilipecosta
Copy link
Member

I'll defer to @mtias here. While this fixes the problem, it seems that the default block's implementation is getting bigger and bigger just to handle legacy issues. While that's totally fine once Gutenberg merged into Core, it's good to try to limit it while not done yet.

Maybe it's fine to keep it for a release or two and then remove it later.

Totally agree here, my expectation is that it would follow our normal deprecation rules. And stay there two releases, as soon as an existing post is open this code coverts it. So most users will not be affected by this change.

@mtias
Copy link
Member Author

mtias commented Mar 20, 2018

Let's keep it then but make sure to remove in a couple releases.

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Blocks Overall functionality of blocks [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants