-
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
Extract the search component into @automattic/search
#46424
Conversation
eab26ea
to
28fe17d
Compare
@automattic/search
packages/search/src/search.tsx
Outdated
tabIndex={ enableOpenIcon ? 0 : undefined } | ||
onKeyDown={ enableOpenIcon ? this.openListener : undefined } | ||
aria-controls={ 'search-component-' + this.instanceId } | ||
aria-label={ __( 'Open Search' ) } |
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.
ℹ️ String reuse speeds up translation and improves consistency. The following string might make a good alternative and has already been translated 31 times:
translate( 'Open Search', { context: 'button label'} )
ES Score: 14
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. |
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.
Nice work @saramarcondes.
I'm definitely interested to see a simplification of this component, but it's probably best to be done in another PR. Maybe we could write some tests and then do the refactor.
Yet, I wonder if refactoring it as uncontrolled makes sense - are we going to keep all the functionality that it already offers?
I've left some thoughts, but this generally looks like pretty solid work!
packages/search/src/search.tsx
Outdated
}; | ||
|
||
UNSAFE_componentWillReceiveProps( nextProps: Props ) { | ||
if ( this.props.isOpen !== nextProps.isOpen ) { |
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.
This feels a bit flawed. The component state can be changed by user intent but could be overridden by another prop change, feels a bit off. I'd expect isOpen
to be used only for the initial state. Is there a real use case where this behavior is necessary?
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.
Maybe there is a valid use case where the parent wants to close the search, no matter what the internal state is?
These cases would maybe be covered if the search component auto-closed when it loses focus -- means that another UI element is now active.
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.
These cases would maybe be covered if the search component auto-closed when it loses focus -- means that another UI element is now active.
For this to work we'd need to always auto-focus. I'm not opposed to that given that a similar component in the new P2 follows this behavior but my understanding is that auto-focusing is undesirable for accessibility reasons. Maybe that doesn't apply for when you're "auto-focusing" on a new element after activating a button that makes the new element appear?
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.
As far as I understand, auto-focusing is always intrusive from an accessibility point of view. I might be wrong, of course - @diegohaz would know best.
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.
Auto-focus is okay if followed by a user action that makes sense. For example, clicking on a <label>
should focus on the input; opening a dialog should move focus into it; deleting the currently focused row in a table could move focus somewhere else (like the next or previous rows).
In this component, I would expect that the input would be focused after clicking on the search icon.
packages/search/src/search.tsx
Outdated
isOpen: this.props.isOpen || false, | ||
} ); | ||
|
||
if ( this.searchInput?.current ) { |
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.
this.searchInput
always exists and always has the current
property. It's the value of current
that can be null
. The optional chaining is not needed.
packages/search/src/search.tsx
Outdated
this.searchInput?.current?.blur(); | ||
this.openIcon?.current?.focus(); | ||
} else { | ||
this.searchInput?.current?.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.
Same here: only the ?.
after current
needs to be optional.
packages/search/src/search.tsx
Outdated
}; | ||
|
||
UNSAFE_componentWillReceiveProps( nextProps: Props ) { | ||
if ( this.props.isOpen !== nextProps.isOpen ) { |
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.
Maybe there is a valid use case where the parent wants to close the search, no matter what the internal state is?
These cases would maybe be covered if the search component auto-closed when it loses focus -- means that another UI element is now active.
packages/search/src/search.tsx
Outdated
hasFocus: this.props.autoFocus, | ||
}; | ||
|
||
UNSAFE_componentWillReceiveProps( nextProps: Props ) { |
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.
A new component shouldn't use the legacy method. The new getDerivedStateFromProps
method is preferred. Or even better, do a functional component with hooks.
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.
This component cannot actually use getDerivedStateFromProps
because it's being controlled externally and internally. It specifically needs to respond to changes in isOpen
. If we turn this into an uncontrolled component then we can remove this logic and replace it with a defaultIsOpen
as you suggested in another comment. Then consumers that need the component to respond to external isOpen
may add a key
prop that depends on their isOpen
value to force the component to re-mount with the new default value.
Maybe it's out of the scope of this PR since this is just a code extraction. But I wonder why the buttons are |
I think its within scope for this PR. I'd like to extract and make significant improvements wherever possible! I'll update that, thanks for pointing it out. (Plus we can use WordPress |
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.
WordPress Desktop CI Failure for job "wp-desktop-mac".
@saramarcondes please inspect this job's build steps for breaking changes at this link. For temporal failures, you may try to "Rerun Workflow from Failed".
Please also ensure this branch is rebased off latest Calypso.
wp-desktop ci passing, closing review
39e04ee
to
b99034d
Compare
tabIndex={ enableOpenIcon ? 0 : undefined } | ||
onKeyDown={ enableOpenIcon ? this.openListener : undefined } | ||
aria-controls={ 'search-component-' + this.instanceId } | ||
aria-label={ __( 'Open Search' ) } |
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.
ℹ️ String reuse speeds up translation and improves consistency. The following string might make a good alternative and has already been translated 31 times:
translate( 'Open Search', { context: 'button label'} )
ES Score: 14
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.
WordPress Desktop CI Failure for job "wp-desktop-mac".
@saramarcondes please inspect this job's build steps for breaking changes at this link. For temporal failures, you may try to "Rerun Workflow from Failed".
Please also ensure this branch is rebased off latest Calypso.
Also add a noop wrapping form to cause soft-keyboards in iOS to display a "search" button instead of "return": https://stackoverflow.com/a/26287843
b99034d
to
fe517fd
Compare
wp-desktop ci passing, closing review
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.
WordPress Desktop CI Failure for job "wp-desktop-mac".
@saramarcondes please inspect this job's build steps for breaking changes at this link. For temporal failures, you may try to "Rerun Workflow from Failed".
Please also ensure this branch is rebased off latest Calypso.
wp-desktop ci passing, closing review
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.
Looking really well and testing great!
I've left some nits, but nothing blocking IMHO.
I think it's already in a great state and we can address any potential improvement in subsequent PRs.
Great work @saramarcondes!
packages/search/package.json
Outdated
}, | ||
"devDependencies": { | ||
"@storybook/addon-actions": "^5.3.18", | ||
"enzyme": "^3.11.0" |
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.
Since we don't have any tests yet, it seems we can remove enzyme
from the list of dependencies, no?
packages/search/package.json
Outdated
@@ -0,0 +1,52 @@ | |||
{ | |||
"name": "@automattic/search", | |||
"version": "1.0.0-alpha.3", |
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.
Nit: we haven't released that yet, should it be 1.0.0-alpha
or beta
or even just 1.0.0
?
packages/search/src/style.scss
Outdated
} | ||
} | ||
|
||
.search__input-fade .search__text-overlay { |
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 wonder if it makes sense to have all the styles nested in .search
, to ensure we're not bleeding any styles beyond the component.
Also, while on that topic, I wonder if it makes sense to change the wrapper .search
to a more specific classname. .search
seems a bit too common, and IMHO there's a big chance to run into conflicts.
packages/search/CHANGELOG.md
Outdated
@@ -0,0 +1,3 @@ | |||
## 1.0.0 | |||
|
|||
- Add Search component |
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.
Should we mention that this based on the existing code in Calypso?
packages/search/src/search.tsx
Outdated
/** | ||
* External dependencies | ||
*/ | ||
import React, { KeyboardEvent, MouseEvent, FocusEvent, FormEvent, ChangeEvent } from 'react'; |
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.
Opinionated nit: would be really great if we can sort the imports, both when they're multiple imports from the same module and multiple imports on multiple lines. Makes it more readable IMHO.
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.
Shall we enable an ESLint rule for sorted imports?
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.
My personal opinion is YES, but maybe now it's not the time. With the new rule that we recently enabled to make all imports relative to the root of Calypso, any change to existing files will be a mess for contributors.
I say let's reconsider doing at some point in the future.
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.
/cc @scinos
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.
Plenty of options if we decide go that route https://www.npmjs.com/search?q=eslint%20sort%20import
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.
WordPress Desktop CI Failure for job "wp-desktop-mac".
@saramarcondes please inspect this job's build steps for breaking changes at this link. For temporal failures, you may try to "Rerun Workflow from Failed".
Please also ensure this branch is rebased off latest Calypso.
wp-desktop ci passing, closing review
"scripts": { | ||
"clean": "npx rimraf dist && tsc --build --clean", | ||
"prepublish": "yarn run clean", | ||
"prepare": "transpile && tsc && copy-assets" |
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.
Hi @saramarcondes 👋 I noticed that even though the search
package is written fully in TypeScript, the transpilation for the dist
directories is done by Babel. The TypeScript compiler is used only to emit the type declarations.
Was this an intentional decision or is it some copy-paste accident? The compilation could be done 100% by the TypeScript compiler, with a pair of tsconfig.json
and tsconfig-cjs.json
config files. The @automattic/data-stores
package does that, for example.
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.
This is an accident of copy and paste from the way the components
package is configured. Please feel free to open a PR to fix this if you can, I’m not really familiar with how it would work without Babel...
Side note: the language picker package probably has the same problem.
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, I'll patch both packages in a PR 👍
Rationale
We need to extract this component so that it can be used inside of the language-picker component. I've also moved it outside of
@automattic/components
for the same reasons laid out here: #46020 (comment)Changes proposed in this Pull Request
This is more or less a 1-to-1 reimplementation of the existing component. There are some differences, however:
form
to cause iOS to display a "Search" button rather than "return"Testing instructions
yarn run search:storybook:start
and ensure the examples work as expected and cover the different use casesNote: I didn't port over the existing tests because they aren't really that useful of tests.
Questions
delaySearch
? That seems like a waste and a supremely unlikely edge case usage). Shall we move forward with turning this component into an uncontrolled component (i.e., by not responding to changes in "static" props and not allowingvalue
to be passed in?)Fixes #