Skip to content

Commit bc3f137

Browse files
authored
ref(nav) move isLinkActive and ProjectIcon inside components (#110372)
Moves ProjectIcon inside Secondary Nav components, moves prop definitions next to component implementations and moves isLinkActive inside the primary nav component.
1 parent 03bcffa commit bc3f137

File tree

10 files changed

+197
-210
lines changed

10 files changed

+197
-210
lines changed

static/app/views/navigation/primary/components.tsx

Lines changed: 94 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Fragment, useEffect, useRef, type MouseEventHandler} from 'react';
22
import {css, useTheme} from '@emotion/react';
33
import styled from '@emotion/styled';
4+
import type {LocationDescriptor} from 'history';
45

56
import type {ButtonProps} from '@sentry/scraps/button';
67
import {Button} from '@sentry/scraps/button';
@@ -11,7 +12,6 @@ import {Tooltip} from '@sentry/scraps/tooltip';
1112
import {DropdownMenu, type MenuItemProps} from 'sentry/components/dropdownMenu';
1213
import {useFrontendVersion} from 'sentry/components/frontendVersionContext';
1314
import {IconDefaultsProvider} from 'sentry/icons/useIconDefaults';
14-
import type {Organization} from 'sentry/types/organization';
1515
import {trackAnalytics} from 'sentry/utils/analytics';
1616
import normalizeUrl from 'sentry/utils/url/normalizeUrl';
1717
import {useLocation} from 'sentry/utils/useLocation';
@@ -24,51 +24,6 @@ import {useNavigationContext} from 'sentry/views/navigation/context';
2424
import {PRIMARY_NAVIGATION_GROUP_CONFIG} from 'sentry/views/navigation/primary/config';
2525
import type {PrimaryNavigationGroup} from 'sentry/views/navigation/types';
2626
import {NavigationLayout} from 'sentry/views/navigation/types';
27-
import {isLinkActive} from 'sentry/views/navigation/utils';
28-
29-
interface SidebarItemLinkProps {
30-
analyticsKey: string;
31-
group: PrimaryNavigationGroup;
32-
to: string;
33-
activeTo?: string;
34-
analyticsParams?: Record<string, unknown>;
35-
children?: React.ReactNode;
36-
}
37-
38-
interface SidebarItemDropdownProps {
39-
analyticsKey: string;
40-
items: MenuItemProps[];
41-
label: string;
42-
analyticsParams?: Record<string, unknown>;
43-
children?: React.ReactNode;
44-
disableTooltip?: boolean;
45-
icon?: React.ReactNode;
46-
onOpen?: MouseEventHandler<HTMLButtonElement>;
47-
size?: ButtonProps['size'];
48-
triggerWrap?: React.ComponentType<{children: React.ReactNode}>;
49-
}
50-
51-
interface SidebarButtonProps {
52-
analyticsKey: string;
53-
label: string;
54-
analyticsParams?: Record<string, unknown>;
55-
buttonProps?: Omit<ButtonProps, 'aria-label'>;
56-
children?: React.ReactNode;
57-
className?: string;
58-
onClick?: MouseEventHandler<HTMLButtonElement>;
59-
}
60-
61-
function recordPrimaryItemClick(
62-
analyticsKey: string,
63-
organization: Organization,
64-
analyticsParams?: Record<string, unknown>
65-
) {
66-
trackAnalytics('navigation.primary_item_clicked', {
67-
item: analyticsKey,
68-
organization,
69-
...analyticsParams,
70-
});
71-
}
7227

7328
interface SidebarItemProps extends React.HTMLAttributes<HTMLLIElement> {
7429
children: React.ReactNode;
@@ -105,9 +60,17 @@ function SidebarItem({children, label, disableTooltip, ref, ...props}: SidebarIt
10560
);
10661
}
10762

108-
// Stable module-level component to avoid remounts when used as `renderWrapAs`
109-
function PassthroughWrapper({children}: {children: React.ReactNode}) {
110-
return children;
63+
interface SidebarMenuProps {
64+
analyticsKey: string;
65+
items: MenuItemProps[];
66+
label: string;
67+
analyticsParams?: Record<string, unknown>;
68+
children?: React.ReactNode;
69+
disableTooltip?: boolean;
70+
icon?: React.ReactNode;
71+
onOpen?: MouseEventHandler<HTMLButtonElement>;
72+
size?: ButtonProps['size'];
73+
triggerWrap?: React.ComponentType<{children: React.ReactNode}>;
11174
}
11275

11376
export function SidebarMenu({
@@ -121,7 +84,7 @@ export function SidebarMenu({
12184
icon,
12285
size,
12386
triggerWrap: TriggerWrap = Fragment,
124-
}: SidebarItemDropdownProps) {
87+
}: SidebarMenuProps) {
12588
// This component can be rendered without an organization in some cases
12689
const organization = useOrganization({allowNull: true});
12790
const {layout} = useNavigationContext();
@@ -160,7 +123,11 @@ export function SidebarMenu({
160123
size={size}
161124
onClick={event => {
162125
if (organization) {
163-
recordPrimaryItemClick(analyticsKey, organization, analyticsParams);
126+
trackAnalytics('navigation.primary_item_clicked', {
127+
item: analyticsKey,
128+
organization,
129+
...analyticsParams,
130+
});
164131
}
165132
triggerProps.onClick?.(event);
166133
onOpen?.(event);
@@ -179,6 +146,41 @@ export function SidebarMenu({
179146
);
180147
}
181148

149+
interface SidebarItemLinkProps {
150+
analyticsKey: string;
151+
group: PrimaryNavigationGroup;
152+
to: string;
153+
activeTo?: string;
154+
analyticsParams?: Record<string, unknown>;
155+
children?: React.ReactNode;
156+
}
157+
158+
export function SidebarLink({
159+
children,
160+
to,
161+
activeTo = to,
162+
analyticsKey,
163+
analyticsParams,
164+
group,
165+
...props
166+
}: SidebarItemLinkProps) {
167+
const label = PRIMARY_NAVIGATION_GROUP_CONFIG[group].label;
168+
169+
return (
170+
<SidebarItem label={label} {...props}>
171+
<SidebarNavigationLink
172+
to={to}
173+
activeTo={activeTo}
174+
analyticsKey={analyticsKey}
175+
analyticsParams={analyticsParams}
176+
group={group}
177+
>
178+
{children}
179+
</SidebarNavigationLink>
180+
</SidebarItem>
181+
);
182+
}
183+
182184
function SidebarNavigationLink({
183185
children,
184186
to,
@@ -190,7 +192,10 @@ function SidebarNavigationLink({
190192
const organization = useOrganization();
191193
const {layout, activePrimaryNavigationGroup} = useNavigationContext();
192194
const location = useLocation();
193-
const isActive = isLinkActive(normalizeUrl(activeTo, location), location.pathname);
195+
const isActive = isSidebarLinkActive(
196+
normalizeUrl(activeTo, location),
197+
location.pathname
198+
);
194199
const label = PRIMARY_NAVIGATION_GROUP_CONFIG[group].label;
195200

196201
// Reload the page when the frontend is stale to ensure users get the latest version
@@ -205,7 +210,11 @@ function SidebarNavigationLink({
205210
aria-current={isActive ? 'page' : undefined}
206211
isMobile={layout === NavigationLayout.MOBILE}
207212
onClick={() => {
208-
recordPrimaryItemClick(analyticsKey, organization, analyticsParams);
213+
trackAnalytics('navigation.primary_item_clicked', {
214+
item: analyticsKey,
215+
organization,
216+
...analyticsParams,
217+
});
209218
}}
210219
{...{
211220
[NAVIGATION_PRIMARY_LINK_DATA_ATTRIBUTE]: true,
@@ -226,30 +235,14 @@ function SidebarNavigationLink({
226235
);
227236
}
228237

229-
export function SidebarLink({
230-
children,
231-
to,
232-
activeTo = to,
233-
analyticsKey,
234-
analyticsParams,
235-
group,
236-
...props
237-
}: SidebarItemLinkProps) {
238-
const label = PRIMARY_NAVIGATION_GROUP_CONFIG[group].label;
239-
240-
return (
241-
<SidebarItem label={label} {...props}>
242-
<SidebarNavigationLink
243-
to={to}
244-
activeTo={activeTo}
245-
analyticsKey={analyticsKey}
246-
analyticsParams={analyticsParams}
247-
group={group}
248-
>
249-
{children}
250-
</SidebarNavigationLink>
251-
</SidebarItem>
252-
);
238+
interface SidebarButtonProps {
239+
analyticsKey: string;
240+
label: string;
241+
analyticsParams?: Record<string, unknown>;
242+
buttonProps?: Omit<ButtonProps, 'aria-label'>;
243+
children?: React.ReactNode;
244+
className?: string;
245+
onClick?: MouseEventHandler<HTMLButtonElement>;
253246
}
254247

255248
export function SidebarButton({
@@ -274,7 +267,11 @@ export function SidebarButton({
274267
className={className}
275268
aria-label={showLabel ? undefined : label}
276269
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
277-
recordPrimaryItemClick(analyticsKey, organization, analyticsParams);
270+
trackAnalytics('navigation.primary_item_clicked', {
271+
item: analyticsKey,
272+
organization,
273+
...analyticsParams,
274+
});
278275
buttonProps.onClick?.(e);
279276
onClick?.(e);
280277
}}
@@ -517,3 +514,22 @@ export const SidebarList = styled('ul')<{isMobile: boolean; compact?: boolean}>`
517514
width: 100%;
518515
}
519516
`;
517+
518+
export function isSidebarLinkActive(
519+
to: LocationDescriptor | string,
520+
pathname: string,
521+
options: {end?: boolean} = {end: false}
522+
): boolean {
523+
const toPathname = normalizeUrl(typeof to === 'string' ? to : (to.pathname ?? '/'));
524+
525+
if (options.end) {
526+
return pathname === toPathname;
527+
}
528+
529+
return pathname.startsWith(toPathname);
530+
}
531+
532+
// Stable module-level component to avoid remounts when used as `renderWrapAs`
533+
function PassthroughWrapper({children}: {children: React.ReactNode}) {
534+
return children;
535+
}

static/app/views/navigation/projectIcon.tsx

Lines changed: 0 additions & 97 deletions
This file was deleted.

0 commit comments

Comments
 (0)