Skip to content

Commit 8cbe7ba

Browse files
reintroduce central search
1 parent 55a256f commit 8cbe7ba

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React, { useState } from "react";
2+
import {
3+
Button,
4+
Dialog,
5+
DialogActions,
6+
DialogContent,
7+
DialogTitle,
8+
TextField,
9+
Grid,
10+
Card,
11+
CardContent,
12+
Typography,
13+
Box,
14+
} from "@mui/material";
15+
import { useRouter } from "next/router";
16+
import { nativeMenuItems } from "/src/layouts/config";
17+
18+
/**
19+
* Recursively collects only leaf items (those without sub-items).
20+
* If an item has an `items` array, we skip that item itself (treat it as a "folder")
21+
* and continue flattening deeper.
22+
*/
23+
function getLeafItems(items = []) {
24+
let result = [];
25+
26+
for (const item of items) {
27+
if (item.items && Array.isArray(item.items) && item.items.length > 0) {
28+
// recurse into children
29+
result = result.concat(getLeafItems(item.items));
30+
} else {
31+
// no child items => this is a leaf
32+
result.push(item);
33+
}
34+
}
35+
36+
return result;
37+
}
38+
39+
export const CippCentralSearch = ({ handleClose, open }) => {
40+
const router = useRouter();
41+
const [searchValue, setSearchValue] = useState("");
42+
43+
// Flatten the menu items once
44+
const flattenedMenuItems = getLeafItems(nativeMenuItems);
45+
46+
const handleChange = (event) => {
47+
setSearchValue(event.target.value);
48+
};
49+
50+
// Optionally handle Enter key
51+
const handleKeyDown = (event) => {
52+
if (event.key === "Enter") {
53+
// do something if needed, e.g., analytics or highlight
54+
}
55+
};
56+
57+
// Filter leaf items by matching title or path
58+
const normalizedSearch = searchValue.trim().toLowerCase();
59+
const filteredItems = flattenedMenuItems.filter((leaf) => {
60+
const inTitle = leaf.title?.toLowerCase().includes(normalizedSearch);
61+
const inPath = leaf.path?.toLowerCase().includes(normalizedSearch);
62+
// If there's no search value, show no results (you could change this logic)
63+
return normalizedSearch ? inTitle || inPath : false;
64+
});
65+
66+
// Helper to bold‐highlight the matched text
67+
const highlightMatch = (text = "") => {
68+
if (!normalizedSearch) return text;
69+
const parts = text.split(new RegExp(`(${normalizedSearch})`, "gi"));
70+
return parts.map((part, i) =>
71+
part.toLowerCase() === normalizedSearch ? (
72+
<Typography component="span" fontWeight="bold" key={i}>
73+
{part}
74+
</Typography>
75+
) : (
76+
part
77+
)
78+
);
79+
};
80+
81+
// Click handler: shallow navigate with Next.js
82+
const handleCardClick = (path) => {
83+
router.push(path, undefined, { shallow: true });
84+
handleClose();
85+
};
86+
87+
return (
88+
<Dialog fullWidth maxWidth="sm" onClose={handleClose} open={open}>
89+
<DialogTitle>CIPP Search</DialogTitle>
90+
<DialogContent />
91+
<DialogContent>
92+
<Box>
93+
<TextField
94+
fullWidth
95+
label="Search any menu item or page in CIPP"
96+
onChange={handleChange}
97+
onKeyDown={handleKeyDown}
98+
value={searchValue}
99+
autoFocus
100+
/>
101+
102+
{/* Show results or "No results" */}
103+
{searchValue.trim().length > 0 ? (
104+
filteredItems.length > 0 ? (
105+
<Grid container spacing={2} mt={2}>
106+
{filteredItems.map((item, index) => (
107+
<Grid item xs={12} sm={12} md={12} key={index}>
108+
<Card
109+
variant="outlined"
110+
sx={{ height: "100%", cursor: "pointer" }}
111+
onClick={() => handleCardClick(item.path)}
112+
>
113+
<CardContent>
114+
<Typography variant="h6">{highlightMatch(item.title)}</Typography>
115+
<Typography variant="body2" color="textSecondary">
116+
Path: {highlightMatch(item.path)}
117+
</Typography>
118+
</CardContent>
119+
</Card>
120+
</Grid>
121+
))}
122+
</Grid>
123+
) : (
124+
<Box mt={2}>No results found.</Box>
125+
)
126+
) : (
127+
<Box mt={2}>
128+
<Typography variant="body2">Type something to search by title or path.</Typography>
129+
</Box>
130+
)}
131+
</Box>
132+
</DialogContent>
133+
<DialogActions>
134+
<Button color="inherit" onClick={handleClose}>
135+
Close
136+
</Button>
137+
</DialogActions>
138+
</Dialog>
139+
);
140+
};

src/layouts/top-nav.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback } from "react";
1+
import { useCallback, useEffect } from "react";
22
import NextLink from "next/link";
33
import PropTypes from "prop-types";
44
import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon";
@@ -11,9 +11,13 @@ import { paths } from "../paths";
1111
import { AccountPopover } from "./account-popover";
1212
import { CippTenantSelector } from "../components/CippComponents/CippTenantSelector";
1313
import { NotificationsPopover } from "./notifications-popover";
14+
import { useDialog } from "../hooks/use-dialog";
15+
import { MagnifyingGlassCircleIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
16+
import { CippCentralSearch } from "../components/CippComponents/CippCentralSearch";
1417
const TOP_NAV_HEIGHT = 64;
1518

1619
export const TopNav = (props) => {
20+
const searchDialog = useDialog();
1721
const { onNavOpen } = props;
1822
const settings = useSettings();
1923
const mdDown = useMediaQuery((theme) => theme.breakpoints.down("md"));
@@ -24,6 +28,23 @@ export const TopNav = (props) => {
2428
});
2529
}, [settings]);
2630

31+
useEffect(() => {
32+
const handleKeyDown = (event) => {
33+
if ((event.metaKey || event.ctrlKey) && event.key === "k") {
34+
event.preventDefault();
35+
openSearch();
36+
}
37+
};
38+
window.addEventListener("keydown", handleKeyDown);
39+
return () => {
40+
window.removeEventListener("keydown", handleKeyDown);
41+
};
42+
}, []);
43+
44+
const openSearch = () => {
45+
searchDialog.handleOpen();
46+
};
47+
2748
return (
2849
<Box
2950
component="header"
@@ -85,6 +106,12 @@ export const TopNav = (props) => {
85106
</SvgIcon>
86107
</IconButton>
87108
)}
109+
<IconButton color="inherit" onClick={() => openSearch()}>
110+
<SvgIcon color="action" fontSize="small">
111+
<MagnifyingGlassIcon />
112+
</SvgIcon>
113+
</IconButton>
114+
<CippCentralSearch open={searchDialog.open} handleClose={searchDialog.handleClose} />
88115
<NotificationsPopover />
89116
<AccountPopover
90117
onThemeSwitch={handleThemeSwitch}

0 commit comments

Comments
 (0)