-
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
FontSizePicker: tidy up internal logic #63553
Conversation
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
expect( | ||
screen.queryByLabelText( 'Custom' ) | ||
).not.toBeInTheDocument(); |
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 didn't write a more specific assertion on which UI is presented to the user since I initially wrote this unit test so that it would pass on trunk
, where no input UI is actually presented to the user in this case (a bug IMO — see a more extended explanation in the PR description).
Since the input UI presented to the user changes depending on the length of the fontSizes
prop, if we think that we should test that part too, I will duplicate this test and add it to two different sections of the file
packages/components/CHANGELOG.md
Outdated
@@ -6,17 +6,18 @@ | |||
|
|||
- `Button`: Never apply `aria-disabled` to anchor ([#63376](https://github.com/WordPress/gutenberg/pull/63376)). | |||
|
|||
### Enhancements |
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.
Moved the Enhancements
section before the Internal
section
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 couldn't help but think the useEffect
is avoidable 🤔 How I model this in my brain, there is only one bit of state ("is the user requesting a custom input"), and the currentPickerType
can be derived without a useEffect
/useState
.
diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx
index a547a26de5..8f5fba2be0 100644
--- a/packages/components/src/font-size-picker/index.tsx
+++ b/packages/components/src/font-size-picker/index.tsx
@@ -8,7 +8,7 @@ import type { ForwardedRef } from 'react';
*/
import { __ } from '@wordpress/i18n';
import { settings } from '@wordpress/icons';
-import { useState, useEffect, forwardRef } from '@wordpress/element';
+import { useState, forwardRef } from '@wordpress/element';
/**
* Internal dependencies
@@ -107,27 +107,7 @@ const UnforwardedFontSizePicker = (
);
const isCustomValue = !! value && ! selectedFontSize;
- const [ currentPickerType, setCurrentPickerType ] = useState(
- getPickerType( ! disableCustomFontSizes && isCustomValue, fontSizes )
- );
-
- useEffect( () => {
- setCurrentPickerType(
- getPickerType(
- // If showing the custom value picker, switch back to predef only
- // if `disableCustomFontSizes` is set to `true`.
- currentPickerType === 'custom'
- ? ! disableCustomFontSizes
- : ! disableCustomFontSizes && isCustomValue,
- fontSizes
- )
- );
- }, [
- currentPickerType,
- disableCustomFontSizes,
- isCustomValue,
- fontSizes,
- ] );
+ const [ userRequestedCustom, setUserRequestedCustom ] = useState( false );
const units = useCustomUnits( {
availableUnits: unitsProp,
@@ -137,6 +117,13 @@ const UnforwardedFontSizePicker = (
return null;
}
+ const currentPickerType = getPickerType(
+ // If showing the custom value picker, switch back to predef only
+ // if `disableCustomFontSizes` is set to `true`.
+ ! disableCustomFontSizes && ( userRequestedCustom || isCustomValue ),
+ fontSizes
+ );
+
const headerHint = getHeaderHint(
currentPickerType,
selectedFontSize,
@@ -180,14 +167,9 @@ const UnforwardedFontSizePicker = (
: __( 'Set custom size' )
}
icon={ settings }
- onClick={ () => {
- setCurrentPickerType(
- getPickerType(
- currentPickerType !== 'custom',
- fontSizes
- )
- );
- } }
+ onClick={ () =>
+ setUserRequestedCustom( ! userRequestedCustom )
+ }
isPressed={ currentPickerType === 'custom' }
size="small"
/>
@@ -215,9 +197,7 @@ const UnforwardedFontSizePicker = (
);
}
} }
- onSelectCustom={ () =>
- setCurrentPickerType( 'custom' )
- }
+ onSelectCustom={ () => setUserRequestedCustom( true ) }
/>
) }
{ currentPickerType === 'togglegroup' && (
@@ -251,6 +231,8 @@ const UnforwardedFontSizePicker = (
hideLabelFromVision
value={ value }
onChange={ ( newValue ) => {
+ setUserRequestedCustom( true );
+
if ( newValue === undefined ) {
onChange?.( undefined );
} else {
@@ -281,6 +263,8 @@ const UnforwardedFontSizePicker = (
initialPosition={ fallbackFontSize }
withInputField={ false }
onChange={ ( newValue ) => {
+ setUserRequestedCustom( true );
+
if ( newValue === undefined ) {
onChange?.( undefined );
} else if ( hasUnits ) {
This will fail the "doesn't hide custom input when the selected font size is a predef" tests. However, I'm not sure this behavior is necessarily wrong? If the user never requested or interacted with the custom inputs, maybe it is more natural to fall back to the predef pickers if the value changed programatically. What do you think?
@@ -29,24 +31,25 @@ export type FontSizePickerProps = { | |||
/** | |||
* Available units for custom font size selection. | |||
* | |||
* @default `[ 'px', 'em', 'rem' ]` | |||
* @default [ 'px', 'em', 'rem' ] |
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 noticed this was out of date, fix at #63577.
setCurrentPickerType( | ||
getPickerType( | ||
currentPickerType !== 'custom', | ||
fontSizes |
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.
If we moved the getPickerType
and getHeaderHint
inside the component function, we wouldn't need to pass fontSizes
all over the place. We could also potentially reuse some of the conditions (like whether the current picker type is custom
for example), or even remove some of the utilities (like shouldUseSelectOverToggle()
). Maybe that could reduce some of the complexity?
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.
T_SHIRT_NAMES[ fontSizes.indexOf( selectedFontSize ) ] | ||
); | ||
} | ||
return ''; |
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 know this is pre-existing, but I wasn't sure why in this case we don't display the common unit (like we do below) and if this is intentional.
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'm not sure if this was intentional or not — I would probably need to look in the git history and see if this aspect was discussed actively in previous PRs. Probably out of scope for this refactor, though — for now, I've decided to keep the ''
fallback around to preserve the same behavior as on trunk
.
* | ||
* @default false | ||
*/ | ||
withSlider?: boolean; | ||
/** | ||
* If `true`, a reset button will be displayed alongside the input field | ||
* when a custom font size is active. Has no effect when | ||
* `disableCustomFontSizes` or `withSlider` is `true`. |
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.
Good catch, seems like this was wrong.
36d04b2
to
7d0b69d
Compare
That's an interesting way to rethink the component's logic.
It's not necessarily wrong, but there may be value in showing the user the current value via the custom picker, rather than a blank togglegroup/select control. Imagine a user setting a custom font size, and later re-accessing those settings — wouldn't be weird not to see that custom font size reflected in the UI? It could give the wrong impression that the setting was not recorded. Another unintended side-effect of the proposed change (which was apparently not covered by unit tests 😱 I added one! ) is that the user is not able to switch to predef inputs when a custom value (ie. non-predef) is entered in the custom UI: Kapture.2024-07-16.at.12.14.23.mp4I went ahead and made a tweak to the code you suggested (this diff is meant to be applied on top of the diff previously suggested by you), which should be closer to the original behavior (causing all existing unit tests to pass). diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx
index 8f5fba2be0..7085487adb 100644
--- a/packages/components/src/font-size-picker/index.tsx
+++ b/packages/components/src/font-size-picker/index.tsx
@@ -107,7 +107,9 @@ const UnforwardedFontSizePicker = (
);
const isCustomValue = !! value && ! selectedFontSize;
- const [ userRequestedCustom, setUserRequestedCustom ] = useState( false );
+ // Initially request a custom picker if the value is not from the predef list.
+ const [ userRequestedCustom, setUserRequestedCustom ] =
+ useState( isCustomValue );
const units = useCustomUnits( {
availableUnits: unitsProp,
@@ -120,7 +122,7 @@ const UnforwardedFontSizePicker = (
const currentPickerType = getPickerType(
// If showing the custom value picker, switch back to predef only
// if `disableCustomFontSizes` is set to `true`.
- ! disableCustomFontSizes && ( userRequestedCustom || isCustomValue ),
+ ! disableCustomFontSizes && userRequestedCustom,
fontSizes
);
|
7d67b56
to
1340d11
Compare
if ( commonUnit ) { | ||
return `(${ commonUnit })`; | ||
const headerHint = useMemo( () => { | ||
switch ( currentPickerType ) { |
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.
Swapped the previous if
-based logic to a switch
-based logic looking at the value of currentPickerType
, which should be much simpler to follow
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.
Looks good and tests well, thanks for the refinements! The logic is now easier to follow, we have better test coverage, and we have better behavior for the edge cases 🚀
…actor to switch statement.
1340d11
to
30e5c64
Compare
* Add unit tests * Refactor code and extract logic * Sync JS docs * CHANGELOG * Further simplify logic * Inline `getPickerType` utility * Inline `getHeaderHint` and `shouldUseSelectOverToggle` utilities, refactor to switch statement. * toBeInTheDocument() => toBeVisible() * Move units back at the start of the render function for a smaller diff --- Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: tyxla <tyxla@git.wordpress.org>
What?
Extract some of the changes from #63040 to tidy up
FontSizePicker
's internal logicWhy?
The component's internal logic for which UI to show is based on several boolean flags spread across the file, which makes it difficult to read/understand and bug-prone.
How?
This reactor extracts the logic to a few utility functions and condenses the UI state into one internal state variable.
Testing Instructions
trunk
and on this PRFontSizePicker
in Storybook, especially around the props that influence what UI is presented to the user:fontSizes
(>= 5 font sizes =>CustomSelectControl
, < 5 font sizes =>ToggleGroupControl
)disableCustomFontSizes
should disable the custom UI and hide the toggleNote
The new PR introduces a slight behavioral change to the component, which is IMO an improvement. Here's how to reproduce:
FontSizePicker
Storybook exampledisableCustomFontSizes
prop totrue
trunk
, no UI is presented to the user. On this PR, eitherToggleGroupControl
orCustomSelectControl
UIs are shows based on how many font sizes the user can pick from.