-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Stacked Nav #77665
base: master
Are you sure you want to change the base?
Stacked Nav #77665
Conversation
Bundle ReportChanges will increase total bundle size by 25.42kB (0.09%) ⬆️. This is within the configured threshold ✅ Detailed changes
|
}; | ||
} | ||
|
||
export function useNavItems(): NavItemsResult { |
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 useNavItems
hook acts as a single source of truth for the entire nav, including nested navigation and feature flag checks.
// oganization is loaded before rendering children. Organization may not be | ||
// loaded yet when this first renders. | ||
const organization = useOrganization({allowNull: true}); | ||
const hasNavigationV2 = organization?.features.includes('navigation-sidebar-v2'); | ||
const App = hasNavigationV2 ? AppLayout : LegacyAppLayout; |
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.
Something to watch out for: this note calls out that "Organization may not be loaded yet when this first renders".
For this experience to exist behind a feature flag, the organization has to be loaded. This could potentially cause some layout thrashing once the org loads.
<AppContainer className="app"> | ||
<Nav /> | ||
<BodyContainer> | ||
{organization && <OrganizationHeader organization={organization} />} | ||
{organization && <DevToolInit />} | ||
<Body>{children}</Body> | ||
<Footer /> | ||
</BodyContainer> | ||
</AppContainer> |
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.
Notable: adding a new wrapper for the main content allows us to remove the position: fixed
CSS for the sidebar. This means that we no longer have to hardcode sidebar width and manually adjust padding for the content offset.
Codecov ReportAttention: Patch coverage is ✅ All tests successful. No failed tests found. Additional details and impacted files@@ Coverage Diff @@
## master #77665 +/- ##
==========================================
- Coverage 78.12% 78.10% -0.02%
==========================================
Files 6996 7003 +7
Lines 310224 310429 +205
Branches 50762 50796 +34
==========================================
+ Hits 242351 242461 +110
- Misses 56142 56226 +84
- Partials 11731 11742 +11 |
e6ab2eb
to
edfa094
Compare
31cf655
to
70e3698
Compare
7105bf5
to
73773de
Compare
7946948
to
7cc38b5
Compare
const [view, setView] = useState<'closed' | 'primary' | 'secondary'>('closed'); | ||
const handleClick = useCallback(() => { | ||
setView(value => (value === 'closed' ? 'primary' : 'closed')); | ||
}, [setView]); | ||
|
||
const location = useLocation(); | ||
|
||
useEffect(() => { | ||
setView('closed'); | ||
}, [location.pathname, setView]); | ||
|
||
useEffect(() => { | ||
const body = document.body; | ||
const app = document.querySelector('.app > :not(nav)'); | ||
if (view !== 'closed') { | ||
app?.setAttribute('inert', ''); | ||
body.style.setProperty('overflow', 'hidden'); | ||
} else { | ||
app?.removeAttribute('inert'); | ||
body.style.removeProperty('overflow'); | ||
} | ||
}, [view]); |
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 is generally preferable to use useLayoutEffect when doing such changes, useEffect might result in subtle flickering
You can get rid of this last useEffect if you call the body attrib logic inside the handler, it's generally preferred to do that as useEffects are annoying to follow
onViewChange = useCallback(view => {
setView(view)
updateNavStyleAttributes(view) // can be defined outside of the component
},[setView])
useEffect...
onViewChange('closed')
[onViewChange]
Can we use a dedicated query selector and target the element directly (it would make it a bit more robust than having to rely on the dom selector). Right now if the selector fails, then logic will silently fail, but I think we want to log this as the nav should always be present?
const handlePointerMove = (e: PointerEvent<HTMLUListElement>) => { | ||
if (!(e.target instanceof Element)) return; | ||
const a = e.target.closest('a'); | ||
const list = e.target.closest('ul'); | ||
if (!(a && list)) { | ||
hover.current = null; | ||
animation.current = {duration: 0.01}; | ||
return; | ||
} | ||
if (hover.current === a) return; | ||
animation.current = {type: 'spring', mass: 0.1, damping: 10, stiffness: 150}; | ||
hover.current = a; | ||
const newY = a?.getBoundingClientRect()?.y ?? 0; | ||
const offset = list.getBoundingClientRect()?.y ?? 0; | ||
setY(newY - offset); | ||
}; |
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 going to be a performance problem (not sure to what extend), but we are triggering style and layout by calling getBoundingClientRect on both elements as well as using state to store the y variable. Since this will fire on pointer move, it will create a ton of updates
Is there a way you can do this without using state and calling bounding rects or using setState to track y coordinate?
|
||
mockUseLocation.mockReturnValue({...LocationFixture()}); | ||
}); | ||
|
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.
afterEach(() => ConfigStore.reset()) |
I think you can also reset the store
export interface NavItemsResult { | ||
primary: { | ||
body: SidebarItem[]; | ||
footer: SidebarItem[]; | ||
}; | ||
secondary: { | ||
body: SubmenuItem[]; | ||
footer: SubmenuItem[]; | ||
}; | ||
} |
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.
export interface NavItemsResult { | |
primary: { | |
body: SidebarItem[]; | |
footer: SidebarItem[]; | |
}; | |
secondary: { | |
body: SubmenuItem[]; | |
footer: SubmenuItem[]; | |
}; | |
} | |
export interface NavItemsResult { | |
primary: { | |
body: ReadonlyArray<SidebarItem>; | |
footer: ReadonlyArray<SidebarItem>; | |
}; | |
secondary: { | |
body: ReadonlyArray<SidebarItem>; | |
footer: ReadonlyArray<SidebarItem>; | |
}; | |
} |
I think you want to make sure these are not mutated in any component as it would impact the rendering
|
||
interface NavItem { | ||
label: string; | ||
check?: FeatureCheck; |
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 comment about reusing the feature check signature if possible
indicatorProps: HTMLAttributes<HTMLSpanElement>; | ||
} | ||
|
||
export function useIndicator() { |
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.
export function useIndicator() { | |
export function useNavIndicator() { |
@@ -33,20 +37,59 @@ function OrganizationLayout({children}: Props) { | |||
// oganization is loaded before rendering children. Organization may not be | |||
// loaded yet when this first renders. | |||
const organization = useOrganization({allowNull: true}); | |||
const hasNavigationV2 = organization?.features.includes('navigation-sidebar-v2'); | |||
const App = hasNavigationV2 ? AppLayout : LegacyAppLayout; |
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.
Thank you for not using V2 to name the layout as well :D
Co-authored-by: Jonas <jonas.badalic@sentry.io>
Update side navigation to new Stacked UI, behind the
navigation-sidebar-v2
flag.Rather than the approach taken in #76868, this PR implements the new navigation as an entirely new set of components in the
components/nav/
directory. This new experience is rendered from thestatic/app/views/organizationLayout/index.tsx
file. The intention is that the existingcomponents/sidebar/
directory would be removed when this feature flag reaches GA.Todo