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

New block: Favorite button #635

Merged
merged 4 commits into from
Jul 16, 2024
Merged

New block: Favorite button #635

merged 4 commits into from
Jul 16, 2024

Conversation

ryelle
Copy link
Contributor

@ryelle ryelle commented Jul 2, 2024

The favorite button is used across the pattern, theme, and plugin directories. This PR combines the Favorite block from the Theme Directory & Pattern Directory to create a shared component that can be used across all 3 sites.

See #617

To solve the issue of each directory having their own favoriting method, this block adds a simple API endpoint to create/delete, which calls callbacks that the child themes can set up to handle the favorite process. If they're not set up, the API errors, but doesn't do anything visually. The callbacks receive the current post ID & request object, and can return true (success), a WP_Error (failure), or the new count of favorites (also a success).

This uses the same filter process as our other blocks (Query Filters, Ratings, etc) to set up configuration, including the callbacks. Child themes should filter wporg_favorite_button_settings, and return:

Type Name Description
callable add_callback Callback function to handle adding the current item to a user's favorites. The function will be passed the post ID and request object. It should return a WP_Error, true, or the updated count of favorite items.
callable delete_callback Callback function to handle removing the current item from a user's favorites. Same arguments and return value as the add_callback.
int count Number of times this item has been favorited.
bool is_favorite Check if the current item is favorited.

The count can be omitted if showCount is not used.

Attributes

  • showCount, default false
    Controls whether the favorite count is visible. If this is used, the callback should return the favorite count, and settings should include count.
  • variant
    The block has two variants, default (outlined button, on single patterns & themes) and small (no outline, small padding, on patterns grid).

See https://github.com/WordPress/wporg-theme-directory/compare/try/mu-plugins-favorite-button, https://github.com/WordPress/pattern-directory/compare/try/mu-plugins-favorite-button for examples in use.

Screenshots

Themes (no visual changes)

Before After
themes-before-non themes-after-non
themes-before-fav themes-after-fav

Patterns (keeping the count)

Before After
patterns-before-grid patterns-after-grid
patterns-before-single-non patterns-after-single-non
patterns-before-single-fav patterns-after-single-fav

To test:

It's probably easiest to test with one of the other repos, I've set up the new block in themes and patterns in the following branches:

There are site-specific test instructions in the above PRs ^

@jasmussen
Copy link
Collaborator

Looks good, and is, I believe, where consensus arrived. 👍 👍

return new \WP_Error(
'favorite-failed',
// Users should never see this error, so we can leave it untranslated.
'Unable to favorite this item.',
Copy link
Contributor

@adamwoodnz adamwoodnz Jul 4, 2024

Choose a reason for hiding this comment

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

Will $result include any more info that might be useful to a developer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If $result is a WP_Error (maybe-useful error info), it's returned as-is in the conditional above. Really, the only reason anyone would reach this code is if the callbacks aren't implemented… which is probably a good error message for devs. I'll update it 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated error messaging: 0ebb8f2

$show_count = $attributes['showCount'] ?? false;
$variant = $attributes['variant'] ?? 'default';

if ( ! $user_id && ! $show_count && 'small' !== $variant ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we display anything if the user is logged out? I think I get the state this is aimed at, but I wonder if we should bail early if there is no user_id

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the block displays favorite count, it should appear regardless of login state (there's code below to switch between a button & span wrapper, if the user is logged in). If the block is not showing the favorite count, it should be hidden for non-logged-in users, because it would just be a heart icon.

The idea is to still display the favorite count, even if the user can't interact with it.

I can remove the variant detection though, that was leftover from before the variant + count display were split out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simplified condition slightly by removing the variant check: 2dcb8ee

},
"variant": {
"type": "string",
"enum": [ "default", "small" ],
Copy link
Contributor

Choose a reason for hiding this comment

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

For me the variants are more obvious as

Suggested change
"enum": [ "default", "small" ],
"enum": [ "default", "no-outline" ],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does also use a smaller padding, and is designed for "small" spaces, so I would still prefer small. Along the lines of the is-small helper class for buttons.

Comment on lines 56 to 72
&.is-heart-outline {
display: block;
}

&.is-heart-filled {
display: none;
}
}

&.is-favorite {
svg.is-heart-outline {
display: none;
}

svg.is-heart-filled {
display: block;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Use logic in render to switch this instead, to reduce DOM?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've switched this to use the interactivity API + a binding on hidden, which simplifies the CSS at least. Since the JS doesn't re-render, just update, both icons need to be on the page to switch between. ad75039

Copy link
Contributor

@adamwoodnz adamwoodnz left a comment

Choose a reason for hiding this comment

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

Looking good! Tested on Pattern directory and appears to work, although my existing count data (from repo setup) got cleared, not sure why:

Kapture.2024-07-05.at.10.54.19.mp4

A few comments inline.

@StevenDufresne
Copy link
Contributor

Looking at @adamwoodnz's video, that button state flashes black. If that's the case, can we prevent that?

@ryelle
Copy link
Contributor Author

ryelle commented Jul 5, 2024

that button state flashes black. If that's the case, can we prevent that?

That's the active state, it was designed that way for all buttons. Here are two other places this happens (the first two I happened to grab in my recording view):

button-active.mp4

although my existing count data (from repo setup) got cleared, not sure why:

It re-tallies all the users who have a pattern favorited when updating the count, so when you update it locally, the count goes back to 1 as you're the only user on the local site to favorite it.

@StevenDufresne
Copy link
Contributor

That's the active state, it was designed that way for all buttons. Here are two other places this happens (the first two I happened to grab in my recording view):

I understand that but it feels distracting in this case. Looking at Adam's video, the third button doesn't have that flash (is that a video problem?) and it feels like a better experience.

@ryelle
Copy link
Contributor Author

ryelle commented Jul 6, 2024

Looking at Adam's video, the third button doesn't have that flash (is that a video problem?) and it feels like a better experience.

It's probably a faster click, the active state only appears as long as you're holding the mouse button down. Is the active state a blocker for this PR? If so, we'll probably need a different active state from design.

I'm still planning on addressing @adamwoodnz's feedback, I appreciate the suggestions!

@jasmussen
Copy link
Collaborator

I'd tend to agree, that active state is a bit curious. I wonder: do we actually need one? CC: @fcoveram as I think you have past knowledge on buttons.

@fcoveram
Copy link
Collaborator

fcoveram commented Jul 8, 2024

I've been adding the active state to almost all new components created in the Design Library to follow what was already implemented. I'm unaware of any principle we're attached to or any discussions on pros and cons.

@ryelle
Copy link
Contributor Author

ryelle commented Jul 8, 2024

I'd tend to agree, that active state is a bit curious. I wonder: do we actually need one?

I believe they're useful on mobile where there is no hover state, so active works to indicate what you tapped. I've also seen that it can be helpful for users with mobility issues, to make it clear when the click is happening. That said, we could probably make it less dramatic, either here or in general.

@ryelle ryelle merged commit c944d1e into trunk Jul 16, 2024
2 checks passed
@ryelle ryelle deleted the add/block-favorite-button branch July 16, 2024 15:34
@ryelle
Copy link
Contributor Author

ryelle commented Jul 16, 2024

I've merged this to move forward, but if we want to update anything (like the active state), let's continue that in a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
5 participants