diff --git a/web/src/App.jsx b/web/src/App.jsx
index e7e642ac4..eb55bafde 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -11,13 +11,13 @@ import { Router } from 'preact-router';
import Sidebar from './Sidebar';
import StyleGuide from './StyleGuide';
import Api, { FetchStatus, useConfig } from './api';
-import { DarkModeProvider, SidebarProvider } from './context';
+import { DarkModeProvider, DrawerProvider } from './context';
export default function App() {
const { data, status } = useConfig();
return (
-
+
{status !== FetchStatus.LOADED ? (
@@ -41,7 +41,7 @@ export default function App() {
)}
-
+
);
}
diff --git a/web/src/Sidebar.jsx b/web/src/Sidebar.jsx
index 6821a946c..94aceec30 100644
--- a/web/src/Sidebar.jsx
+++ b/web/src/Sidebar.jsx
@@ -1,56 +1,45 @@
import { h, Fragment } from 'preact';
-import Link from './components/Link';
import LinkedLogo from './components/LinkedLogo';
-import { Link as RouterLink } from 'preact-router/match';
-import { useCallback, useState } from 'preact/hooks';
-import { useSidebar } from './context';
-
-function NavLink({ className = '', href, text, ...other }) {
- const external = href.startsWith('http');
- const El = external ? Link : RouterLink;
- const props = external ? { rel: 'noopener nofollow', target: '_blank' } : {};
- return (
-
- {text}
-
- );
-}
+import { Match } from 'preact-router/match';
+import { memo } from 'preact/compat';
+import { useConfig } from './api';
+import NavigationDrawer, { Destination, Separator } from './components/NavigationDrawer';
+import { useCallback, useMemo } from 'preact/hooks';
export default function Sidebar() {
- const { showSidebar, setShowSidebar } = useSidebar();
-
- const handleDismiss = useCallback(() => {
- setShowSidebar(false);
- }, [setShowSidebar]);
+ const { data: config } = useConfig();
+ const cameras = useMemo(() => Object.keys(config.cameras), [config]);
return (
-
- {showSidebar ? : ''}
-
-
+ }>
+
+
+ {({ matches }) =>
+ matches ? (
+
+
+ {cameras.map((camera) => (
+
+ ))}
+
+
+ ) : null
+ }
+
+
+
+
+
-
diff --git a/web/src/components/Link.jsx b/web/src/components/Link.jsx
index dbe0518b0..3547996d7 100644
--- a/web/src/components/Link.jsx
+++ b/web/src/components/Link.jsx
@@ -1,9 +1,16 @@
import { h } from 'preact';
+import { Link as RouterLink } from 'preact-router/match';
-export default function Link({ className, children, href, ...props }) {
+export default function Link({
+ activeClassName = '',
+ className = 'text-blue-500 hover:underline',
+ children,
+ href,
+ ...props
+}) {
return (
-
+
{children}
-
+
);
}
diff --git a/web/src/components/Menu.jsx b/web/src/components/Menu.jsx
index 831b6c920..dc521a44e 100644
--- a/web/src/components/Menu.jsx
+++ b/web/src/components/Menu.jsx
@@ -6,7 +6,7 @@ export default function Menu({ className, children, onDismiss, relativeTo }) {
return relativeTo ? (
;
+ return
;
}
diff --git a/web/src/components/NavigationDrawer.jsx b/web/src/components/NavigationDrawer.jsx
new file mode 100644
index 000000000..fc6d08d6e
--- /dev/null
+++ b/web/src/components/NavigationDrawer.jsx
@@ -0,0 +1,61 @@
+import { h, Fragment } from 'preact';
+import { Link } from 'preact-router/match';
+import { useCallback, useState } from 'preact/hooks';
+import { useDrawer } from '../context';
+
+export default function NavigationDrawer({ children, header }) {
+ const { showDrawer, setShowDrawer } = useDrawer();
+
+ const handleDismiss = useCallback(() => {
+ setShowDrawer(false);
+ }, [setShowDrawer]);
+
+ return (
+
+ {showDrawer ? : ''}
+
+ {header ? (
+
+ {header}
+
+ ) : null}
+
+
+
+
+ );
+}
+
+export function Destination({ className = '', href, text, ...other }) {
+ const external = href.startsWith('http');
+ const props = external ? { rel: 'noopener nofollow', target: '_blank' } : {};
+
+ const { setShowDrawer } = useDrawer();
+
+ const handleDismiss = useCallback(() => {
+ setTimeout(() => {
+ setShowDrawer(false);
+ }, 250);
+ }, [setShowDrawer]);
+
+ const styleProps = {
+ [external
+ ? 'className'
+ : 'class']: 'block p-2 text-sm font-semibold text-gray-900 rounded hover:bg-blue-500 dark:text-gray-200 hover:text-white dark:hover:text-white focus:outline-none ring-opacity-50 focus:ring-2 ring-blue-300',
+ };
+
+ return (
+
+
{text}
+
+ );
+}
+
+export function Separator() {
+ return
;
+}
diff --git a/web/src/components/RelativeModal.jsx b/web/src/components/RelativeModal.jsx
index 705db416b..d6b031226 100644
--- a/web/src/components/RelativeModal.jsx
+++ b/web/src/components/RelativeModal.jsx
@@ -1,5 +1,6 @@
import { h, Fragment } from 'preact';
import { createPortal } from 'preact/compat';
+import { DarkModeProvider } from '../context';
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
const WINDOW_PADDING = 20;
@@ -75,7 +76,7 @@ export default function RelativeModal({ className, role = 'dialog', children, on
}, [show, position.width, ref.current]);
const menu = (
-
+
{children}
-
+
);
return portalRoot ? createPortal(menu, portalRoot) : menu;
diff --git a/web/src/context/index.jsx b/web/src/context/index.jsx
index 06ae4dfaf..40ff9f560 100644
--- a/web/src/context/index.jsx
+++ b/web/src/context/index.jsx
@@ -65,14 +65,14 @@ export function useDarkMode() {
return useContext(DarkMode);
}
-const Sidebar = createContext(null);
+const Drawer = createContext(null);
-export function SidebarProvider({ children }) {
- const [showSidebar, setShowSidebar] = useState(false);
+export function DrawerProvider({ children }) {
+ const [showDrawer, setShowDrawer] = useState(false);
- return
{children};
+ return
{children};
}
-export function useSidebar() {
- return useContext(Sidebar);
+export function useDrawer() {
+ return useContext(Drawer);
}