-
Notifications
You must be signed in to change notification settings - Fork 888
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
Optimized the new Welcome UI and style fixes #16164
Conversation
935cd3d
to
bb8cf67
Compare
A Storybook has been deployed to preview UI for the latest push |
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 looks really nice when I run it locally. Just a couple of questions and very minor changes.
@@ -55,43 +63,65 @@ function AnimatedBackground (props: { children?: JSX.Element }) { | |||
.to(stars04, { transform: 'scale(1.5)' }) | |||
|
|||
setScenes({ s1, s2 }) | |||
}, []) | |||
}, [isReadyForAnimation]) |
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 content of the function does not use isReadyForAnimation
- should it?
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.
No, the content should not use isReadyForAnimation
.
We check for ref.current
inside the body of useEffect. If the handle of ref is null then it won't create WAAPI objects.
<S.Box ref={isReadyForAnimation ? ref : null}>
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.
Perhaps that wasn't clear. I should revise this to add another condition. What do you think?
if (!ref.current) return
if (!isReadyForAnimation) 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.
Well either way it needs a comment. But yes it seems clearer to have all the logic for that in 1 place. The ref
attribute doesn't need to know whether animation will be used on that ref, so maybe it's clearer to do make that decision based on isReadyForAnimation
in in this effect... otherwise we're splitting the logic....
The other option you could consider, which may be better is not using React.useRef
for this and instead using React.useCallback((ref) => {...
and passing that to the ref prop in the JSX. That will avoid having to use a ref but sync an effect on a prop. You would perform the contents of both effects in the callback when the incoming ref
param is truthy.
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.
added anther check. an easier route.
lastAnimationEl.addEventListener('finish', () => props.onLoad?.()) | ||
|
||
s1.play() | ||
}, [isReadyForAnimation]) |
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.
ditto
@@ -62,7 +62,7 @@ export const BrowserListBox = styled.div` | |||
justify-content: center; | |||
align-items: center; | |||
flex-direction: column; | |||
color: ${(p) => p.theme.color.text01}; | |||
color: #212529; |
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.
Why are we changing some of these SC themed props to css variables and some to direct values?
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 text color on this item should be consistent regardless of the theme. A hardcoded value makes sense.
The border color on the item changes based on if the user has selected or not. A css variable here makes sense to manipulate.
I'm not completely sure what you mean here.
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.
In some places in this PR, you have changed from SC theme prop (e.g. p.theme.color.interactive05
) to css theme variable (e.g. var(--interactive5)
) and in others you have changed from SC theme prop (e.g. p.theme.color.text01
) to the value (e.g. #212529
). I was wondering what the purpose of that inconsistency 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.
var(--interactive5)
is used to decorate the checkboxes. I saw this being available in root and utilized it. The hardcoded value #212529
wasn't.
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 still not sure why sometimes we're using {(p) => p.theme.color.interactive08
(and not --interactive8
from app.global.scss), and sometimes we're using --interactive5
but not p.theme.color.interactive05
and that you've made a conscious decision to change some and not others 🤷
If you don't want the colors to ever change in dark / light then it's best to not use any of the theme variables.
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.
How did you resolve this?
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 went with using hardcoded color values.
@@ -63,7 +63,7 @@ export const MainBox = styled.div` | |||
` | |||
|
|||
export const ProfileListBox = styled.div` | |||
color: ${(p) => p.theme.color.text01}; | |||
color: #212529; |
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.
ditto
height: 100%; | ||
|
||
&.initial { | ||
scale: 0.9; |
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.
What does this achieve? I'm not seeing that there is a transition on this property. Perhaps a comment as to what the purpose of .initial
is would help.
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.
When we animate specific styles of an element, we define an initial state. This depends on the type of animation, of course.
The initial state shouldn't be part of the base styles because we perform a check to determine whether animations should play or not. You can see this being expressed below:
<div className={classnames({ 'view-content': true, 'initial': shouldPlayAnimations })}>
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.
Right but there isn't a transition on scale, so does this just jump from 0.9 to 1 when the initial class is removed?
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.
Yes, however, the initial class will never be removed like that.
shouldPlayAnimations
computes on prefers reduced motion and load time variable which is during startup and is a one-shot. We also hide with opacity so the element isn't visible.
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 still not understanding why we have scale: 0.9
or when it will get removed.
Edit: Oh I see it does get animated in the styles contained in the react component. I guess that's why I suggested putting the KeyFrames in this file, close to the initial styles.
A Storybook has been deployed to preview UI for the latest push |
76a53d7
to
e77fab6
Compare
A Storybook has been deployed to preview UI for the latest push |
@@ -55,43 +63,65 @@ function AnimatedBackground (props: { children?: JSX.Element }) { | |||
.to(stars04, { transform: 'scale(1.5)' }) | |||
|
|||
setScenes({ s1, s2 }) | |||
}, []) | |||
}, [isReadyForAnimation]) |
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.
Well either way it needs a comment. But yes it seems clearer to have all the logic for that in 1 place. The ref
attribute doesn't need to know whether animation will be used on that ref, so maybe it's clearer to do make that decision based on isReadyForAnimation
in in this effect... otherwise we're splitting the logic....
The other option you could consider, which may be better is not using React.useRef
for this and instead using React.useCallback((ref) => {...
and passing that to the ref prop in the JSX. That will avoid having to use a ref but sync an effect on a prop. You would perform the contents of both effects in the callback when the incoming ref
param is truthy.
@@ -62,7 +62,7 @@ export const BrowserListBox = styled.div` | |||
justify-content: center; | |||
align-items: center; | |||
flex-direction: column; | |||
color: ${(p) => p.theme.color.text01}; | |||
color: #212529; |
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.
In some places in this PR, you have changed from SC theme prop (e.g. p.theme.color.interactive05
) to css theme variable (e.g. var(--interactive5)
) and in others you have changed from SC theme prop (e.g. p.theme.color.text01
) to the value (e.g. #212529
). I was wondering what the purpose of that inconsistency is...
height: 100%; | ||
|
||
&.initial { | ||
scale: 0.9; |
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.
Right but there isn't a transition on scale, so does this just jump from 0.9 to 1 when the initial class is removed?
@@ -64,10 +64,10 @@ export function useProfileCount () { | |||
} | |||
|
|||
export function useShouldPlayAnimations () { | |||
const [shouldPlayAnimations] = React.useState( | |||
const [shouldPlayAnimations] = React.useState(() => ( |
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 think we should have a comment here on why this isn't a module-level variable. Since it never changes, I keep wondering that when I read it. Someone might see this and want to change it.
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.
commented
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.
What is "unexpected behavior and potential bugs"?
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.
Declaring a variable at the module level can make it accessible to all components, which can make it difficult to track changes and test components in isolation. In general, it is better to keep all variables within React's state system, even if they are not expected to change, as this allows React to manage them and ensures that components are able to re-render correctly when state changes.
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.
Declaring a variable at the module level can make it accessible to all components, which can make it difficult to track changes and test components in isolation.
Your hook is already accessible at the module level, accessible to all components, is it not?
export const SHOULD_PLAY_ANIMATIONS = (
loadTimeData.getBoolean('hardwareAccelerationEnabledAtStartup') &&
!window.matchMedia('(prefers-reduced-motion: reduce)').matches
)
is no different in terms of accessibility, except it recognises that it's a constant that doesn't get re-evaluated from every different component which uses it (unlike a hook).
it is better to keep all variables within React's state system, even if they are not expected to change
Why is that? I can understand maybe for tests, but I don't think that applies here.
this allows React to manage them and ensures that components are able to re-render correctly when state changes
If it cannot change then there is no re-render when it changes
Perhaps there was a timing issue calling loadTimeData.getBoolean at the module level (I believe it needs to wait for /strings.js
to load first which fills loadTimeData with data)? Or (less likely) perhaps there was an issue calling matchMedia
in the module root? If those were the issues, or something similar, then that could be a more meaningful comment.
However, if something like that is the issue, then you can avoid the multiple rendering by having the "constnat" lazy-evaluated on its first call by a react function.
let shouldPlayAnimations: undefined | boolean = undefined
export function GetShouldPlayAnimations () {
return (shouldPlayAnimations !== undefined)
? shouldPlayAnimations
: (shouldPlayAnimations = (
loadTimeData.getBoolean('hardwareAccelerationEnabledAtStartup') &&
!window.matchMedia('(prefers-reduced-motion: reduce)').matches
))
}
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.
changed this to module-level variable
A Storybook has been deployed to preview UI for the latest push |
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.
Please address comments, but functionally everything is good
iOS CI looks like a transient failure. I restarted it |
db786ef
to
abdff2d
Compare
A Storybook has been deployed to preview UI for the latest push |
* Converted assets to webp format * Lazy loading * Style fixes * Added background image loading handle
Verification PASSED on
|
Example |
Example |
Example |
---|---|---|
Test Case #2
- brave/brave-browser#27061
Went through the STR/Cases outlined via brave/brave-browser#27061 (comment) and verified that the animations under brave://welcome
has improved greatly compared to before as per the following:
- ensured that
brave://welcome
loads without any issues/jank when launching a new profile- ensured that the page rendered correctly and the animations appeared to be smooth
- ensured that the window didn't appear "black" when initially loading
brave://welcome
- ensured that the animations are smooth when running through the different screens under
brave://welcome
New.Tab.-.Brave.2022-12-15.01-01-17.mp4
Created brave/brave-browser#27396 as there's still some issues when resizing the window.
Test Case #2
- brave/brave-browser#27105
Went through the STR/Cases outlined via brave/brave-browser#27105 (comment) and ensured that all the text via brave://welcome
is visible/legible when using a dark theme
as per the following:
Example |
Example |
Example |
Example |
Example |
Example |
---|---|---|---|---|---|
* Converted assets to webp format * Lazy loading * Style fixes * Added background image loading handle
Resolves brave/brave-browser#26851
Resolves brave/brave-browser#27060
Resolves brave/brave-browser#27061
Resolves brave/brave-browser#27105
This PR optimizes the new Welcome UI and fixes a few style issues:
Note: Please ensure to test this with
--disable-gpu
as wellSubmitter Checklist:
QA/Yes
orQA/No
;release-notes/include
orrelease-notes/exclude
;OS/...
) to the associated issuenpm run test -- brave_browser_tests
,npm run test -- brave_unit_tests
,npm run lint
,npm run gn_check
,npm run tslint
git rebase master
(if needed)Reviewer Checklist:
gn
After-merge Checklist:
changes has landed on
Test Plan: