Merge pull request #17984 from chanzuckerberg/master

feat: allow making isLocationMatch use exact match in query string matcher
This commit is contained in:
Patrik Oldsberg
2023-06-06 14:49:41 +02:00
committed by GitHub
5 changed files with 44 additions and 5 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Added `exact` prop to `SidebarSubmenuItem` which causes it to only highlight if the current location is an exact match.
+1
View File
@@ -1087,6 +1087,7 @@ export type SidebarSubmenuItemProps = {
to?: string;
icon?: IconComponent;
dropdownItems?: SidebarSubmenuItemDropdownItem[];
exact?: boolean;
};
// @public
@@ -130,6 +130,7 @@ export type SidebarSubmenuItemProps = {
to?: string;
icon?: IconComponent;
dropdownItems?: SidebarSubmenuItemDropdownItem[];
exact?: boolean;
};
/**
@@ -138,7 +139,7 @@ export type SidebarSubmenuItemProps = {
* @public
*/
export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => {
const { title, subtitle, to, icon: Icon, dropdownItems } = props;
const { title, subtitle, to, icon: Icon, dropdownItems, exact } = props;
const classes = useStyles();
const { setIsHoveredOn } = useContext(SidebarItemWithSubmenuContext);
const closeSubmenu = () => {
@@ -146,7 +147,7 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => {
};
const toLocation = useResolvedPath(to ?? '');
const currentLocation = useLocation();
let isActive = isLocationMatch(currentLocation, toLocation);
let isActive = isLocationMatch(currentLocation, toLocation, exact);
const [showDropDown, setShowDropDown] = useState(false);
const handleClickDropdown = () => {
@@ -155,7 +156,7 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => {
if (dropdownItems !== undefined) {
dropdownItems.some(item => {
const resolvedPath = resolvePath(item.to);
isActive = isLocationMatch(currentLocation, resolvedPath);
isActive = isLocationMatch(currentLocation, resolvedPath, exact);
return isActive;
});
return (
@@ -95,4 +95,30 @@ describe('isLocationMatching', () => {
toLocation = { pathname: '/catalog', search: '', hash: '' };
expect(isLocationMatch(currentLocation, toLocation)).toBe(true);
});
describe('exact matching', () => {
it('return false when target query parameters are subset of current location query parameters', async () => {
currentLocation = {
pathname: '/catalog',
search: '?x=foo&y=bar',
state: null,
hash: '',
key: '',
};
toLocation = { pathname: '/catalog', search: '?x=foo', hash: '' };
expect(isLocationMatch(currentLocation, toLocation, true)).toBe(false);
});
it('return true when target query parameters are exact match with current location query parameters', async () => {
currentLocation = {
pathname: '/catalog',
search: '?x=foo&y=bar',
state: null,
hash: '',
key: '',
};
toLocation = { pathname: '/catalog', search: '?y=bar&x=foo', hash: '' };
expect(isLocationMatch(currentLocation, toLocation, true)).toBe(true);
});
});
});
@@ -18,7 +18,11 @@ import { Location, Path } from 'history';
import { isEqual, isMatch } from 'lodash';
import qs from 'qs';
export function isLocationMatch(currentLocation: Location, toLocation: Path) {
export function isLocationMatch(
currentLocation: Location,
toLocation: Path,
exact: boolean = false,
) {
const toDecodedSearch = new URLSearchParams(toLocation.search).toString();
const toQueryParameters = qs.parse(toDecodedSearch);
@@ -27,9 +31,11 @@ export function isLocationMatch(currentLocation: Location, toLocation: Path) {
).toString();
const currentQueryParameters = qs.parse(currentDecodedSearch);
const queryStringMatcher = exact ? isEqual : isMatch;
const matching =
isEqual(toLocation.pathname, currentLocation.pathname) &&
isMatch(currentQueryParameters, toQueryParameters);
queryStringMatcher(currentQueryParameters, toQueryParameters);
return matching;
}