-
Notifications
You must be signed in to change notification settings - Fork 23
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
#8293 Unavailable overlay for sidebar forms and temp panels on nav #8351
#8293 Unavailable overlay for sidebar forms and temp panels on nav #8351
Conversation
src/sidebar/ConnectedSidebar.tsx
Outdated
@@ -98,7 +104,21 @@ const ConnectedSidebar: React.VFC = () => { | |||
const listener = useConnectedListener(); | |||
const sidebarIsEmpty = useSelector(selectIsSidebarEmpty); | |||
|
|||
// `useAsyncEffect` will run once on component mount since listener and formsRef don't change on renders. | |||
const navigationListener = useCallback( |
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.
For readability, I'd recommend moving this and the listener registration to separate hook because it's an independent concern. As-is, it's weaved in with the other listener registration code. E.g., useHostNavigationInvalidationListener()
src/sidebar/ConnectedSidebar.tsx
Outdated
@@ -132,10 +152,13 @@ const ConnectedSidebar: React.VFC = () => { | |||
// To avoid races with panel registration, listen after reserving the initial panels. | |||
addListener(listener); | |||
|
|||
browser.webNavigation.onBeforeNavigate.addListener(navigationListener); |
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.
You'll probably need to add mocks for tests.
Also, instead of putting the MV3 condition in the handler, I'd recommend conditionally adding the listener instead
@@ -24,6 +24,7 @@ | |||
|
|||
.tabContainer { | |||
flex-wrap: nowrap; | |||
position: relative; |
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's the change for? Will this break any overflow/scroll logic inside panels?
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 needed so that the blur overlay only covers the panel content area, and does not cover the whole sidebar (including the tabs and logo).
As far as I can tell, it has no other impact on UX such as overflow and scrolling. I can make a note 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.
I'm far from a CSS expert, but it makes sense to me that a "container" element would have position: relative
, right? As in this exact use-case, elements inside the "container" should expect to be able to declare their position relative to the container rather than the entire sidebar window.
} | ||
|
||
.modal-button { | ||
border-radius: 4px; |
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.
@BrandonPxBx is this a different border radius than other buttons in the UI? Is that intentional or just an artifact of the Figma design?
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.
@twschiller Most likely an artifact of the Figma components not matching production components. What's the normal radius on our buttons in the code base? I'll update the Figma components to match.
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 walkthrough modal here (accessed from the quickbar, "Learn: Open the page Editor"):
Is dynamic for some reason, and is around 4px border radius
Other modal components in our storybook are at 4.8px
https://pixiebrix-extension-storybook.vercel.app/?path=/story/components-confirmationmodal--custom-content
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 like there's stuff all over the place in the product.
Default bootstrap settings
- Large - 0.3 rem (4.8 px @ 16px default font size)
- Small - 0.2 rem (3.2 px @ 16px default font size)
Some other buttons
- Refresh icon button on Integration Select filed in Page Editor - 4px hardcoded
- Refresh button on the Preview Pane in the Data Panel on a Trigger Starter Brick - 3.2px hard coded
- Icon buttons on the mod list panel - 4px hardcoded
- Add property in K:V data type input - 4px hardcoded
Because the "system" in Figma is based on the Bootstrap theme, but we cannot use REM values as measurements in Figma, I think there's been some lost in translation throughout the design life cycle.
TL;DR - Lets go with default bootstrap of 0.2 rem on the small buttons.
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 about the border-radius for the modal dialog container? In the mock-up you used 12px, but that doesn't seem to line up with the other modals and bootstrap default.
src/sidebar/UnavailableOverlay.tsx
Outdated
<div className={styles["unavailable-overlay"]}> | ||
<div | ||
className={cx("modal show position-static w-auto h-auto")} | ||
style={{ display: "block", marginTop: "10vh" }} |
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: why not use CSS styles file 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.
I can move it into the scss file, just missed it
src/sidebar/UnavailableOverlay.tsx
Outdated
style={{ display: "block", marginTop: "10vh" }} | ||
> | ||
<Modal.Dialog | ||
size={"sm"} |
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: {
not required for string constants
src/sidebar/UnavailableOverlay.tsx
Outdated
import { Button, Modal } from "react-bootstrap"; | ||
|
||
const UnavailableOverlay = () => ( | ||
<div className={styles["unavailable-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.
NIT: IIRC, in other places we write using unavailableOverlay
so we can just do styles.unavailableOverlay
. Any reason to prefer kebab-case?
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've seen some inconsistent naming pattern in scss files between camel and dash case for class names, but I'm happy to switch back to camel case
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.
IMO - CSS Modules should use camel case because the classes are used directly in js code, and because these values don't actually get written to the DOM, and regular scss files should prefer kebab case because that's what will actually be added to the DOM attribute.
src/sidebar/UnavailableOverlay.tsx
Outdated
> | ||
<Modal.Dialog | ||
size={"sm"} | ||
className={cx(styles["modal-dialog"], "shadow text-center")} |
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: I prefer not mixing utility classes and class names for a component because it splits the concern across 2 locations. Although we don't have a great way to reference the utility classes in the CSS 🤷 so perhaps the code re-use better for non-trivial classes like shadow
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.
Yea, my concern is re-usability and consistency. I'll see if it looks better if I move it into the styles file.
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 mix these in a lot of places already, along with className
pass-through
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 general policy I follow is: "don't mix on the same component, but OK to use both CSS module and utility class in the same file". But again, just a NIT
* Determines if the panel cannot be displayed for the current tab. Used | ||
* to show an overlay over the panel to indicate it is unavailable. | ||
*/ | ||
isUnavailable?: boolean; |
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: adding @since 1.8.14
can be a good way for developers to get a sense of the type/API evolution. (And also, e.g., adding a comment that we added the field to account for MV3 side panel that persists across page redirects/navigation)
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: given that the value isn't persisted anywhere (so no migration is required), should we just make it required to simplify checks? In general, avoiding nullness where possible is probably "a good thing™"
In this case it's not so bad because false/null mean the same thing
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.
Does it need to be designed as a negative boolean? Any chance we can go with isAvailable
instead for simplicity's sake? Does that make the logic worse or something?
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 easier to make this not required since hunting down all the definitions and factories and updating them to include an explicit isUnavailable attribute is somewhat tedious and slightly impacts readability. As you noted, it's almost functionally the same, except for cases when for some reason, we would want to check for the existence of the attribute itself in the type.
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.
That makes sense to me as long as it doesn't make strict-null-checks substantially harder in some way
@@ -203,7 +203,7 @@ const createConfig = (env, options) => | |||
}), | |||
|
|||
// Only notifies when watching. `zsh-notify` is suggested for the `build` script | |||
options.watch && | |||
!isProd(options) && |
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.
❤️
…el-is-no-longer-available-overlay-on-info-panelsforms
* Close form panels | ||
* @param nonces panel nonces | ||
*/ | ||
async function closeForms(nonces: UUID[]): Promise<void> { |
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 don't think calling cancelForm is necessary (or desireable) given that the forms won't exist on the form controller after the page redirect/navigation (because there will be a new content script and formController is a module level variable)
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 that case, it will be a no-op. See:
* Cancel some forms. Is a NOP if a form is no longer registered. |
I guess we could check the panel and if panel.isUnavailable
then we skip calling closeForms
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 form/panel registration logic already needs to handle invalid/unregistered nonces in order to pass strict null checks, so we can be confident that a bug will not be introduced here in the future. Adding the if-check here means that this thunk is now essentially tightly coupled with behavior in other parts of the redux slice, with no change in functionality of this thunk. I think we should remove the if-check here and let cancelForm
no-op, since it's simpler and less-coupled overall code, with no increased risk.
@twschiller This is also used when the user clicks the close button on the panel, not just on navigation/with unavailable panels.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8351 +/- ##
=======================================
Coverage 73.50% 73.51%
=======================================
Files 1330 1332 +2
Lines 41142 41200 +58
Branches 7657 7665 +8
=======================================
+ Hits 30241 30287 +46
- Misses 10901 10913 +12 ☔ View full report in Codecov by Sentry. |
@@ -32,7 +32,6 @@ test.describe("sidebar effect bricks", () => { | |||
await page.goto("/"); | |||
|
|||
// Ensure the page is focused by clicking on an element before running the keyboard shortcut, see runModViaQuickbar | |||
await page.getByText("Index of /").click(); |
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 refactored the runModViaQuickBar method so we don't have to have these click
calls before it.
@@ -70,7 +70,7 @@ test.describe("sidebar controller", () => { | |||
|
|||
// The focus dialog should not be shown in the iframe. Check after checking the top-level frame | |||
// because it's a positive check for the dialog being shown. | |||
await expect(frame.getByRole("button", { name: "OK" })).not.toBeVisible(); | |||
await expect(frame.getByRole("button", { name: "OK" })).toBeHidden(); |
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.
Unrelated lint error fixed
async function waitForBackgroundPageRequest( | ||
context: BrowserContext, | ||
extensionId: string, | ||
errorServiceEndpoint: string, | ||
) { |
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.
refactored this method in the process of fixing an eslint error for no conditionals
@@ -161,6 +164,15 @@ export async function waitForSelectionMenuReadiness(page: Page) { | |||
}).toPass({ timeout: 5000 }); | |||
} | |||
|
|||
// Waits for the quick bar to be ready to use | |||
async function waitForQuickBarReadiness(page: Page) { |
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.
Without this check, sometimes the quickbar would not open when immediately attempting to open it after loading a new page.
await waitForEffect(); | ||
|
||
// The navigation listener should be added for MV3 | ||
expect( | ||
browser.webNavigation.onBeforeNavigate.addListener, | ||
).toHaveBeenCalledWith(expect.any(Function)); |
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.
Does this have to use waitForEffect()
? Could this be done instead as:
await waitForEffect(); | |
// The navigation listener should be added for MV3 | |
expect( | |
browser.webNavigation.onBeforeNavigate.addListener, | |
).toHaveBeenCalledWith(expect.any(Function)); | |
await waitFor(() => { | |
// The navigation listener should be added for MV3 | |
expect( | |
browser.webNavigation.onBeforeNavigate.addListener, | |
).toHaveBeenCalledWith(expect.any(Function)); | |
}); |
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.
Turns out it's not really even needed
const topLevelFrame = await getConnectedTarget(); | ||
cancelForm(topLevelFrame, ...nonces); |
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.
Any particular reason to extract these two lines into a separate, local (private) function here? Why not just inline 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.
Mostly just following the pattern set by the other removeTemporaryPanel.ts
thunk, but I agree it can be inlined
@@ -38,11 +38,13 @@ const activateModPanelEntryFactory = define<ModActivationPanelEntry>({ | |||
}, | |||
], | |||
heading: (n: number) => `Activate Mods Test ${n}`, | |||
isUnavailable: false, |
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.
Just curious, if this isn't required, then we can probably leave these values off the factories, right?
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, we could.
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 the one who should approve the design/UX parts here, but this is looking good from a code perspective to me 👍 Happy to unblock if needed but will defer to someone else here to approve based on the design/copy.
@BrandonPxBx has reviewed the final UX! |
No loom links were found in the first post. Please add one there if you'd like to it to appear on Slack. Do not edit this comment manually. |
What does this PR do?
npm run watch
command.Discussion
Demo
Screen.Recording.2024-04-26.at.4.42.19.PM.mov
TODO
Checklist