-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[Site Admin]: introduce <SiteIcon />
component
#99540
[Site Admin]: introduce <SiteIcon />
component
#99540
Conversation
Jetpack Cloud live (direct link)
Automattic for Agencies live (direct link)
|
This PR does not affect the size of JS and CSS bundles shipped to the user's browser. Generated by performance advisor bot at iscalypsofastyet.com. |
c0d8308
to
0648b4b
Compare
<SiteIcon />
component
7915143
to
b38b55a
Compare
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 100% sure on where we are with the "staying as close as we can to the Core implementations" idea. This code pretty much looks like a fork of the Core implementation, and yet I see many issues which (if we were to address) cannot be addressed by minor tweaks that are easily diff-able with Core. Even if we're not thinking about merging things back to Core, there will still be difficulties merging in Core updates back into our fork.
Reading the principles in #99473 again after seeing this code, I wonder how we're going to balance "optimize for our needs" and "maintaining consistency with Core". Maybe one approach is to not care too much about maintaining consistency?
(My comments are non-blocking, I understand we're in the quick iteration phase.)
const { isRequestingSite, siteIconUrl } = useSelect( ( select ) => { | ||
const { getEntityRecord } = select( coreDataStore ); | ||
const siteData = getEntityRecord( 'root', '__unstableBase', undefined ); | ||
|
||
const site_icon_url = getEntityRecord< { | ||
site_icon_url: string; | ||
} >( 'root', '__unstableBase' )?.site_icon_url; | ||
|
||
return { | ||
isRequestingSite: ! siteData, | ||
siteIconUrl: site_icon_url, | ||
}; | ||
}, [] ); |
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 generally skeptical about UI components that are internally coupled to a store — is this going to be the case for all the other UI components as well? This one is pretty simple but I'm sure there are more complex ones. I feel like it would be easier to test and maintain if there was one level of separation. (One base component with props, another wrapped component with the store subscription.)
Also related, I'm seeing that the SiteIcon
component is exported publicly. This implies to me that the component is going to be reused by consumers in custom UIs. That doesn't quite click with me when I see the tight store coupling, and the pretty specific CSS conditionals with the external selectors in the stylesheet.
If it doesn't make sense to publicly export the component, then it may be overkill to have to maintain a Storybook story for it. A unit test may be better suited to ensure that the store connection is working as expected.
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.
Core seems to have a clear intent to make these components store-dependent, not just this one but others I’ve worked with.
Decoupling them for flexibility isn’t a bad idea, but I believe the goal behind keeping them tightly coupled is to prevent improper usage.
This does make testing more restrictive—especially in Storybook—but it’s not a blocker, just a headache. In the current Storybook setup, we mock the store to showcase how the component adapts to different data states.
Also related, I'm seeing that the SiteIcon component is exported publicly.
That's a good point. I'm going to remove it.
A unit test may be better suited to ensure that the store connection is working as expected.
Something that we could do in a follow-up?
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 guess the use case in this PR doesn't make a great example of why the store coupling is necessary, but if you could provide some examples of what we're planning to do, that could be helpful.
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.
The only good example I can offer is the current implementation of the Site Editor in core.
The core version of this component and others pull data from the store.
I will research/ask those who implemented that at some point what the motivation is.
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 don't have a good use case for any feature, let's consider untangling / removing / cleaning up. Now that we're creating new components it only makes sense to start fresh and clean.
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.
Sorry, I think we’re not quite on the same page regarding the development of this package. The initial idea is simple: to base our implementation on core’s approach to ensure consistency. This allows us to build on what I consider solid foundations.
If we later find that something isn’t quite right, we can always adapt or even remove it, but I don’t think this is the right stage to do that.
In this particular case, I don’t have a concrete answer as to why we are pulling properties from the store instead of passing them as props to the SiteIcon component. However, this implementation exists in core, which suggests it has been thoroughly researched and developed with a clear purpose. I’m not advocating for blindly following core, but at this point, I’d rather rely on an established solution than introduce changes without a strong justification.
Additionally, this PR already includes significant research and testing. We have identified the minimal and necessary resources needed for the new WooCommerce Analytics admin interface, a standalone plugin we are developing. While I can’t guarantee that this implementation will scale perfectly with future needs, the results so far have been more than satisfactory.
That said, I’d like to flip the question: why do you think it’s better to propagate these data points via props instead of retrieving them from the store? I believe this is more about understanding the engineering approach used in this implementation as a whole.
From my perspective, it makes much more sense for the component to adapt to the context in which it is instantiated automatically. In this case, the component is called SiteIcon, which suggests that it represents the site’s icon. Why should we manually define the icon’s URL when we know it’s not an arbitrary value but something that exclusively depends on the global configuration?
If what is needed is a generic component to render an image or an icon, then a component designed specifically for that purpose should be used instead.
I hope this clarifies my perspective.
packages/site-admin/src/components/site-icon/stories/index.stories.tsx
Outdated
Show resolved
Hide resolved
packages/site-admin/src/components/site-icon/stories/index.stories.tsx
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
.site-admin-editor__view-mode-toggle button:focus { |
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.
It's not great that a .site-admin-editor__view-mode-toggle
selector is living in this file. .site-admin-layout.is-full-canvas
is also a bit worrisome. Might be something to refactor eventually.
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.
Makes sense. Let's add a note about it, and refactoring when introducing the other components.
packages/site-admin/src/components/site-icon/stories/style.stories.scss
Outdated
Show resolved
Hide resolved
e0c2fdb
to
5d81c76
Compare
Thanks for your review, Lena. It’s constructive and touches on the most critical aspects of this implementation. Regarding how closely we should align with Core in the long run, the key takeaway (based on input from other developers involved) is to prioritize compatibility regarding UI/UX, without a strict requirement to follow the same technical implementations. The reason this feels almost like a fork (at least from my perspective) is that I’ve used Core as a development guide. That said, I believe the best strategy is to define clear phases. In this initial stage, the priority is to deliver a functional system within a reasonable timeframe. We’re struggling to gain the right momentum right now, and I think we should focus on reducing friction. Later on, with more time, we can refine the implementation—even if that means deviating from the ideal Core-aligned approach. For example, decoupling components from the store is worth considering, but I don’t think it should be a priority at this stage. Ultimately, the goal isn’t to maintain strict compatibility with Core but to build an independent implementation based on it, evolving it according to our needs. And if it makes sense, we could even consider upstreaming this implementation to Core when it gets an option. |
5d81c76
to
c3ab459
Compare
Understood 👍 I can calibrate my reviews for that. |
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.
🚀
f049a49
to
f1b1a8b
Compare
Co-authored-by: Lena Morita <[email protected]>
Co-authored-by: Lena Morita <[email protected]>
f1b1a8b
to
c0d637e
Compare
This PR modifies the release build for the following Calypso Apps: For info about this notification, see here: PCYsg-OT6-p2
To test WordPress.com changes, run |
/** | ||
* Creates a global mock registry for @wordpress/data | ||
*/ | ||
export const createMockRegistry = ( stores = {} ) => { |
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.
Is there a good reason to have a default stores
value? I don't think so. Some improved types could help ensure we pass a stores
.
export const createMockRegistry = ( stores = {} ) => { | |
export const createMockRegistry = ( stores ) => { |
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.
Going to address it in a follow-up.
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.
name: 'WooCommerce Analytics', | ||
description: | ||
'WooCommerce Analytics is a powerful tool that helps you understand how your store is performing and how you can improve your store’s performance.', | ||
'root/__unstableBase': { site_icon_url: './woo-logo.png' }, |
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.
Can we reuse existing logos from the repo instead of adding new copies?
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 can. I just wanted to isolate the story resources in its folder. I can update you if you think it would be better. 👍
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.
const { isRequestingSite, siteIconUrl } = useSelect( ( select ) => { | ||
const { getEntityRecord } = select( coreDataStore ); | ||
const siteData = getEntityRecord( 'root', '__unstableBase', undefined ); | ||
|
||
const site_icon_url = getEntityRecord< { | ||
site_icon_url: string; | ||
} >( 'root', '__unstableBase' )?.site_icon_url; | ||
|
||
return { | ||
isRequestingSite: ! siteData, | ||
siteIconUrl: site_icon_url, | ||
}; | ||
}, [] ); |
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 guess the use case in this PR doesn't make a great example of why the store coupling is necessary, but if you could provide some examples of what we're planning to do, that could be helpful.
I’m planning to refine and clarify the approach in follow-up PRs. For now, this PR (#99348) serves as a proof of concept, showcasing the full implementation in detail. Breaking it down into smaller pieces is tricky, as the coupling makes it hard to isolate certain parts cleanly. I did attempt it here, but I agree that some aspects might still feel a bit unclear. I’ll keep working on making the plan more digestible. |
# ❗ Branched off from #99492This PR introduces the component, which is heavily inspired by the component defined in Core.
Similar to how most components in the Site Editor implementation (@wordpress/edit-site package) work, this component primarily retrieves and assigns values through stores rather than exposing specific props.
For example, in this particular case, the component does not provide a prop for setting the site icon image. In fact, the only exposed prop is className. The site icon image URL is determined through the store.
Storybook
Due to this approach and to enable proper visual testing of this and future components, this PR also introduces a simple yet efficient mechanism (at least for this component) to mock the store from which the SiteIcon component retrieves the URL for rendering the icon.
A mocked store is created with three different sites, each having a distinct icon. This setup allows switching between test sites (see testing instructions below).
Future PRs can further improve this mocking system.
Close #99531
Close #99479
Proposed Changes
Why are these changes being made?
Testing Instructions
With storybook:
Screen.Recording.2025-02-11.at.12.18.59.PM.mov
Pre-merge Checklist