Skip to content

Commit 6cc4323

Browse files
Merge pull request #414 from Enterwell/feature/sidenav-code-cleanup
Feature/sidenav code cleanup
2 parents 66214fb + f25f0b4 commit 6cc4323

File tree

8 files changed

+198
-178
lines changed

8 files changed

+198
-178
lines changed

apps/docs/components/ExampleSideNav.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import { SideNav, SideNavItem, SideNavItemGroup } from '@enterwell/react-ui';
44
import { Button } from '@mui/material';
55
import { Stack } from '@mui/system';
66
import Image from 'next/image';
7-
import { useSearchParams } from 'next/navigation';
7+
import { useRouter, useSearchParams } from 'next/navigation';
88

99
export function ExampleSideNav() {
10-
const params = useSearchParams();
11-
const selectedItem = params.get('item');
12-
const show = params.get('show') === 'true';
10+
const router = useRouter();
11+
const searchParams = useSearchParams();
12+
const selectedItem = searchParams.get('item');
13+
const show = searchParams.get('show') === 'true';
1314
function setShow(show: boolean) {
14-
const url = new URL(window.location.href);
15-
url.searchParams.set('show', show.toString());
16-
window.history.pushState({}, '', url.toString());
15+
const query = new URLSearchParams(Array.from(searchParams.entries()));
16+
if (!show)
17+
query.delete('show');
18+
else query.set('show', show.toString());
19+
router.push(`?${query.toString()}`);
1720
}
1821

1922
return (
Lines changed: 3 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
1-
import { AppBar, AppBarProps, Collapse, Drawer, List, ListItemButton, ListItemIcon, ListItemText, Paper, Stack, Theme, Toolbar, Typography, useTheme } from '@mui/material';
1+
import { AppBar, AppBarProps, Drawer, List, Stack, Toolbar } from '@mui/material';
22
import useMediaQuery from '@mui/material/useMediaQuery';
3-
import { PropsWithChildren, ReactNode, createContext, useContext } from 'react';
3+
import { ReactNode } from 'react';
44
import { useControllableState } from '@enterwell/react-hooks';
5-
import { AddOutlined, RemoveOutlined } from '@mui/icons-material';
6-
7-
const itemOpacity = 0.7;
8-
const itemHoverOpacity = 0.9;
9-
10-
function itemBackgroundImageHighlight(theme: Theme, amount = 0.05) {
11-
return theme.palette.mode === "dark"
12-
? `linear-gradient(180deg, rgba(255,255,255,${amount}) 0%, rgba(255,255,255,${amount}) 100%)`
13-
: `linear-gradient(180deg, rgba(0,0,0,${amount}) 0%, rgba(0,0,0,${amount}) 100%)`;
14-
}
155

166
/**
177
* The props for the SideNav component
@@ -42,8 +32,7 @@ export type SideNavProps = AppBarProps & {
4232
*/
4333
export function SideNav({ children, sx, width = 230, headerHeight = 65, header, endAdorner, ...rest }: SideNavProps) {
4434
const [navOpen, setNavOpen] = useControllableState(false);
45-
const theme = useTheme();
46-
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
35+
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('md'));
4736

4837
const handleClose = () => setNavOpen(false);
4938

@@ -118,158 +107,3 @@ export function SideNav({ children, sx, width = 230, headerHeight = 65, header,
118107
</>
119108
);
120109
};
121-
122-
/**
123-
* The SideNavItemGroup component props.
124-
* @public
125-
*/
126-
type SideNavItemGroupProps = PropsWithChildren<{
127-
label: string;
128-
expanded?: boolean;
129-
defaultExpanded?: boolean;
130-
}>;
131-
132-
const SideNavItemGroupContext = createContext({ inGroup: false });
133-
134-
/**
135-
* The SideNavItemGroup component.
136-
*
137-
* @param props - The component props.
138-
* @returns The react function component.
139-
* @public
140-
*/
141-
export function SideNavItemGroup({ children, label, expanded: controlledExpanded, defaultExpanded }: SideNavItemGroupProps) {
142-
const [expanded, setExpanded] = useControllableState(controlledExpanded, defaultExpanded ?? false);
143-
const handleToggleExpand = () => setExpanded(!expanded);
144-
const theme = useTheme();
145-
146-
const contextValue = {
147-
inGroup: true
148-
};
149-
150-
return (
151-
<>
152-
<ListItemButton
153-
sx={{
154-
alignItems: 'center',
155-
justifyContent: 'space-between',
156-
padding: '12px 10px',
157-
fontWeight: 600,
158-
opacity: itemOpacity,
159-
'&:hover': {
160-
bgcolor: 'transparent',
161-
opacity: itemHoverOpacity
162-
}
163-
}}
164-
onClick={handleToggleExpand}
165-
>
166-
<ListItemText>
167-
<Typography variant='body2' textTransform="uppercase" fontWeight={600}>
168-
{label}
169-
</Typography>
170-
</ListItemText>
171-
{expanded
172-
? (
173-
<RemoveOutlined
174-
color="primary"
175-
sx={{ fontSize: 20 }}
176-
/>
177-
) : (
178-
<AddOutlined
179-
color="primary"
180-
sx={{ fontSize: 20 }}
181-
/>
182-
)}
183-
</ListItemButton>
184-
<Collapse
185-
in={expanded}
186-
sx={{ width: '100%' }}
187-
>
188-
<SideNavItemGroupContext.Provider value={contextValue}>
189-
<List
190-
disablePadding
191-
sx={{
192-
backgroundImage: itemBackgroundImageHighlight(theme),
193-
borderRadius: '4px'
194-
}}
195-
>
196-
{children}
197-
</List>
198-
</SideNavItemGroupContext.Provider>
199-
</Collapse>
200-
</>
201-
);
202-
}
203-
204-
/**
205-
* The SideNavItem component props.
206-
* @public
207-
*/
208-
export type SideNavItemProps = PropsWithChildren<{
209-
href: string;
210-
icon?: ReactNode;
211-
selected?: boolean;
212-
}>;
213-
214-
/**
215-
* The SideNavItem component
216-
* @param props - The component props
217-
* @returns The react function component.
218-
* @public
219-
*/
220-
export function SideNavItem({ children, href, selected, icon }: SideNavItemProps) {
221-
const groupContext = useContext(SideNavItemGroupContext);
222-
const child = groupContext?.inGroup;
223-
const theme = useTheme();
224-
225-
return (
226-
<ListItemButton
227-
href={href}
228-
selected={selected}
229-
sx={{
230-
paddingX: 1.25,
231-
paddingY: 1.5,
232-
borderRadius: 1,
233-
fontWeight: 600,
234-
gap: 1,
235-
"&.Mui-selected": {
236-
color: 'primary.main',
237-
fill: 'primary.main',
238-
backgroundColor: 'transparent',
239-
backgroundImage: child && selected ? itemBackgroundImageHighlight(theme) : 'none',
240-
"&:hover": {
241-
backgroundColor: 'transparent',
242-
backgroundImage: child ? itemBackgroundImageHighlight(theme) : 'none',
243-
}
244-
},
245-
opacity: child ? itemHoverOpacity : itemOpacity,
246-
':hover': {
247-
opacity: itemHoverOpacity,
248-
backgroundColor: child ? itemBackgroundImageHighlight(theme) : 'transparent'
249-
}
250-
}}>
251-
{icon && (
252-
<ListItemIcon
253-
sx={{
254-
minWidth: 'auto',
255-
// '& > svg': { fill: selected ? 'primaryBase' : 'white' }
256-
}}
257-
>
258-
{icon}
259-
</ListItemIcon>
260-
)}
261-
<ListItemText>
262-
<Typography
263-
variant='body2'
264-
textTransform="uppercase"
265-
fontWeight={600}
266-
sx={{
267-
// color: child && !selected ? whiteBase : 'inherit'
268-
}}
269-
>
270-
{children}
271-
</Typography>
272-
</ListItemText>
273-
</ListItemButton>
274-
);
275-
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { ListItemButton, ListItemIcon, ListItemText, Typography, useTheme } from '@mui/material';
2+
import { PropsWithChildren, ReactNode, useContext } from 'react';
3+
import { SideNavItemGroupContext } from './SideNavItemGroupContext';
4+
import { itemBackgroundImageHighlight, itemHoverOpacity, itemOpacity } from './shared';
5+
6+
/**
7+
* The SideNavItem component props.
8+
* @public
9+
*/
10+
export type SideNavItemProps = PropsWithChildren<{
11+
href: string;
12+
icon?: ReactNode;
13+
selected?: boolean;
14+
}>;
15+
16+
/**
17+
* The SideNavItem component
18+
* @param props - The component props
19+
* @returns The react function component.
20+
* @public
21+
*/
22+
export function SideNavItem({ children, href, selected, icon }: SideNavItemProps) {
23+
const groupContext = useContext(SideNavItemGroupContext);
24+
const isInGroup = groupContext?.inGroup;
25+
const theme = useTheme();
26+
27+
return (
28+
<ListItemButton
29+
href={href}
30+
selected={selected}
31+
sx={{
32+
paddingX: 1.25,
33+
paddingY: 1.5,
34+
borderRadius: 1,
35+
fontWeight: 600,
36+
gap: 1,
37+
"&.Mui-selected": {
38+
color: 'primary.main',
39+
fill: 'primary.main',
40+
backgroundColor: 'transparent',
41+
backgroundImage: isInGroup && selected ? itemBackgroundImageHighlight(theme) : 'none',
42+
"&:hover": {
43+
backgroundColor: 'transparent',
44+
backgroundImage: isInGroup ? itemBackgroundImageHighlight(theme) : 'none',
45+
}
46+
},
47+
opacity: isInGroup ? itemHoverOpacity : itemOpacity,
48+
':hover': {
49+
opacity: itemHoverOpacity,
50+
backgroundColor: isInGroup ? itemBackgroundImageHighlight(theme) : 'transparent'
51+
}
52+
}}>
53+
{icon && (
54+
<ListItemIcon
55+
sx={{
56+
minWidth: 'auto',
57+
// '& > svg': { fill: selected ? 'primaryBase' : 'white' }
58+
}}
59+
>
60+
{icon}
61+
</ListItemIcon>
62+
)}
63+
<ListItemText>
64+
<Typography
65+
variant='body2'
66+
textTransform="uppercase"
67+
fontWeight={600}
68+
sx={{
69+
// color: child && !selected ? whiteBase : 'inherit'
70+
}}
71+
>
72+
{children}
73+
</Typography>
74+
</ListItemText>
75+
</ListItemButton>
76+
);
77+
};
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Collapse, List, ListItemButton, ListItemText, Typography, useTheme } from '@mui/material';
2+
import { PropsWithChildren } from 'react';
3+
import { useControllableState } from '@enterwell/react-hooks';
4+
import { AddOutlined, RemoveOutlined } from '@mui/icons-material';
5+
import { SideNavItemGroupContext } from './SideNavItemGroupContext';
6+
import { itemBackgroundImageHighlight, itemHoverOpacity, itemOpacity } from './shared';
7+
8+
/**
9+
* The SideNavItemGroup component props.
10+
* @public
11+
*/
12+
export type SideNavItemGroupProps = PropsWithChildren<{
13+
label: string;
14+
expanded?: boolean;
15+
defaultExpanded?: boolean;
16+
}>;
17+
18+
/**
19+
* The SideNavItemGroup component.
20+
*
21+
* @param props - The component props.
22+
* @returns The react function component.
23+
* @public
24+
*/
25+
export function SideNavItemGroup({ children, label, expanded: controlledExpanded, defaultExpanded }: SideNavItemGroupProps) {
26+
const [expanded, setExpanded] = useControllableState(controlledExpanded, defaultExpanded ?? false);
27+
const handleToggleExpand = () => setExpanded(!expanded);
28+
const theme = useTheme();
29+
30+
const contextValue = {
31+
inGroup: true
32+
};
33+
34+
return (
35+
<>
36+
<ListItemButton
37+
sx={{
38+
alignItems: 'center',
39+
justifyContent: 'space-between',
40+
padding: '12px 10px',
41+
fontWeight: 600,
42+
opacity: itemOpacity,
43+
'&:hover': {
44+
bgcolor: 'transparent',
45+
opacity: itemHoverOpacity
46+
}
47+
}}
48+
onClick={handleToggleExpand}
49+
>
50+
<ListItemText>
51+
<Typography variant='body2' textTransform="uppercase" fontWeight={600}>
52+
{label}
53+
</Typography>
54+
</ListItemText>
55+
{expanded
56+
? (
57+
<RemoveOutlined
58+
color="primary"
59+
sx={{ fontSize: 20 }}
60+
/>
61+
) : (
62+
<AddOutlined
63+
color="primary"
64+
sx={{ fontSize: 20 }}
65+
/>
66+
)}
67+
</ListItemButton>
68+
<Collapse
69+
in={expanded}
70+
sx={{ width: '100%' }}
71+
>
72+
<SideNavItemGroupContext.Provider value={contextValue}>
73+
<List
74+
disablePadding
75+
sx={{
76+
backgroundImage: itemBackgroundImageHighlight(theme),
77+
borderRadius: '4px'
78+
}}
79+
>
80+
{children}
81+
</List>
82+
</SideNavItemGroupContext.Provider>
83+
</Collapse>
84+
</>
85+
);
86+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createContext } from "react";
2+
3+
export const SideNavItemGroupContext = createContext({ inGroup: false });
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from "./SideNav";
2+
export * from "./SideNavItemGroup";
3+
export * from "./SideNavItem";

0 commit comments

Comments
 (0)