diff --git a/src/app.js b/src/app.js
index 99b85a2..5439cf6 100644
--- a/src/app.js
+++ b/src/app.js
@@ -9,37 +9,37 @@ import {
NotFoundView,
RegistrationView,
TravelView,
+ Knit9View,
} from './views'
import { Header, Footer, Banner } from '@components/layout'
-const menuOptions = [
- {
- path: '/',
- label: 'Home',
- view: ,
- },
+const pastKnits = [
+ { path: '/past-knits/knit9', label: 'KNIT 9'},
+ { path: 'https://learn.fabric-testbed.net/knowledge-base/knit-8-a-fabric-community-workshop/', label: 'KNIT 8' },
+ { path: 'https://learn.fabric-testbed.net/knowledge-base/knit-7-a-fabric-community-workshop/', label: 'KNIT 7' },
{
- path: '/registration',
- label: 'Registration',
- view: ,
+ label: 'Additional KNITs',
+ subsubItems: [
+ { path: 'https://learn.fabric-testbed.net/knowledge-base/knit-6-a-fabric-community-workshop/', label: 'KNIT 6' },
+ { path: 'https://learn.fabric-testbed.net/knowledge-base/knit-5-a-fabric-community-workshop/', label: 'KNIT 5' },
+ { path: 'https://learn.fabric-testbed.net/knowledge-base/knit-winter-21-a-fabric-community-workshop', label: 'KNIT 4' },
+ ],
},
+]
+
+const menuOptions = [
+ { path: '/', label: 'Home' },
+ { path: '/registration', label: 'Registration' },
+ { path: '/travel', label: 'Travel Info' },
+ { path: '/cfa', label: 'Calls for Action' },
+ { path: '/agenda', label: 'Agenda' },
{
- path: '/travel',
- label: 'Travel Info',
- view: ,
+ label: 'Past KNITs',
+ path: '/past-knits', // Parent path
+ subItems: pastKnits,
},
- {
- path: '/cfa',
- label: 'Calls for Action',
- view: ,
- }
- // {
- // path: '/agenda',
- // label: 'Agenda',
- // view: ,
- // }
-]
+];
export const App = () => {
return (
@@ -49,18 +49,15 @@ export const App = () => {
- {
- // we'll build the routes from the main menu items.
- // note this implementation only supports a flat,
- // one-level navigation structure.
- menuOptions.map(({ path, view, label }) => (
-
- ))
- }
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+
+
} />
} />
diff --git a/src/components/icons/chevron-icons.js b/src/components/icons/chevron-icons.js
new file mode 100644
index 0000000..e23b428
--- /dev/null
+++ b/src/components/icons/chevron-icons.js
@@ -0,0 +1,50 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+const ChevronIcon = ({ size = 24, fill = "#000", children, ...rest }) => (
+
+);
+
+export const ChevronUpIcon = props => (
+
+
+
+
+);
+
+export const ChevronDownIcon = props => (
+
+
+
+
+);
+
+export const ChevronLeftIcon = props => (
+
+
+
+);
+
+export const ChevronRightIcon = props => (
+
+
+
+);
+
+const requiredProps = {
+ fill: PropTypes.string,
+ size: PropTypes.number,
+ children: PropTypes.node,
+};
+
+ChevronIcon.propTypes = requiredProps;
diff --git a/src/components/icons/index.js b/src/components/icons/index.js
new file mode 100644
index 0000000..66f8757
--- /dev/null
+++ b/src/components/icons/index.js
@@ -0,0 +1,2 @@
+export * from './chevron-icons'
+export * from './link-icons'
diff --git a/src/components/link/link-icon.js b/src/components/icons/link-icons.js
similarity index 100%
rename from src/components/link/link-icon.js
rename to src/components/icons/link-icons.js
diff --git a/src/components/link/external-link.js b/src/components/link/external-link.js
index af1c493..2d3773c 100644
--- a/src/components/link/external-link.js
+++ b/src/components/link/external-link.js
@@ -1,6 +1,6 @@
import { Fragment } from 'react'
import { BaseLinkPropTypes } from './'
-import { ExternalLinkIcon } from './'
+import { ExternalLinkIcon } from '../icons'
import { Button, Link } from '@mui/joy'
export const ExternalLink = ({ to, children, button, ...props }) => {
@@ -25,8 +25,8 @@ export const ExternalLink = ({ to, children, button, ...props }) => {
target="_blank"
rel="noopener noreferrer"
{ ...props }
- >{ children }
-
+ >{ children }
+
)}
diff --git a/src/components/link/index.js b/src/components/link/index.js
index 905aa86..452a51e 100644
--- a/src/components/link/index.js
+++ b/src/components/link/index.js
@@ -1,4 +1,3 @@
export * from './link'
-export * from './link-icon'
export * from './external-link'
export * from './mail-to-link'
\ No newline at end of file
diff --git a/src/components/link/mail-to-link.js b/src/components/link/mail-to-link.js
index bc5f1b9..49c8d43 100644
--- a/src/components/link/mail-to-link.js
+++ b/src/components/link/mail-to-link.js
@@ -1,6 +1,6 @@
import { Fragment } from 'react'
import { BaseLinkPropTypes } from './'
-import { MailtoLinkIcon } from './'
+import { MailtoLinkIcon } from '../icons'
export const MailtoLink = ({ to, children }) => {
return (
diff --git a/src/components/menu/desktop-menu.js b/src/components/menu/desktop-menu.js
deleted file mode 100644
index b540211..0000000
--- a/src/components/menu/desktop-menu.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Box } from '@mui/joy'
-import { Link } from '@components/link'
-import { menuPropTypes } from './menu'
-
-//
-
-export const DesktopMenu = ({ options = [] }) => {
- return (
- a': {
- width: '100%',
- p: 2,
- textDecoration: 'none',
- color: 'var(--knit-palette-primary-900)',
- textTransform: 'uppercase',
- letterSpacing: '0.5px'
- },
- '[aria-current="page"]': {
- backgroundColor: '#0001',
- },
- }}
- >
- {
- options.map(({ label, path }) => (
-
- { label }
-
- ))
- }
-
- )
-}
-
-DesktopMenu.propTypes = { ...menuPropTypes }
diff --git a/src/components/menu/desktop-menu/index.js b/src/components/menu/desktop-menu/index.js
new file mode 100644
index 0000000..7024c5e
--- /dev/null
+++ b/src/components/menu/desktop-menu/index.js
@@ -0,0 +1,70 @@
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import { NavLink, useLocation } from 'react-router-dom'
+import { ChevronDownIcon } from '../../icons/chevron-icons'
+import { MenuContainer, MenuLink, MenuItem } from './menuComponents'
+import { Submenu, SubmenuHeader } from './submenuComponents'
+
+export const DesktopMenu = ({ options = [] }) => {
+ const location = useLocation() // used to determine active paths
+ const [openSubmenu, setOpenSubmenu] = useState(-1)
+
+ const handleOpenSubmenu = (index) => () => setOpenSubmenu(index)
+ const handleCloseSubmenu = () => setOpenSubmenu(-1)
+
+ // determine if a path is active
+ const isActive = (path, subItems) => {
+ if (location.pathname === path) return true; // direct match
+ if (subItems) {
+ return subItems.some((item) =>
+ location.pathname.startsWith(item.path)
+ )
+ }
+ return false
+ }
+
+ return (
+
+ {options.map(({ label, path, subItems }, index) => (
+
+ ))}
+
+ )
+}
+
+DesktopMenu.propTypes = {
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string,
+ subItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string.isRequired,
+ })
+ ),
+ })
+ ),
+}
\ No newline at end of file
diff --git a/src/components/menu/desktop-menu/menuComponents.js b/src/components/menu/desktop-menu/menuComponents.js
new file mode 100644
index 0000000..b993921
--- /dev/null
+++ b/src/components/menu/desktop-menu/menuComponents.js
@@ -0,0 +1,49 @@
+import PropTypes from 'prop-types'
+import { styled } from '@mui/system'
+import { Box, List } from '@mui/joy'
+import { Link } from '@components/link'
+
+export const MenuContainer = ({ children }) => (
+
+ {children}
+
+)
+
+MenuContainer.propTypes = {
+ children: PropTypes.node.isRequired,
+}
+
+export const MenuItem = styled(Box)({
+ position: 'relative',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: '100%',
+ backgroundColor: 'transparent',
+ transition: 'background-color 250ms',
+ '&:hover': {
+ backgroundColor: '#0001',
+ color: 'var(--knit-palette-primary-dark)',
+ },
+})
+
+export const MenuLink = styled(Link)({
+ padding: '0.5rem 1rem',
+ textDecoration: 'none',
+ textTransform: 'uppercase',
+ letterSpacing: '0.75px',
+ color: 'var(--knit-palette-primary-900)',
+ '&[aria-current="page"]': {
+ backgroundColor: '#0002',
+ },
+})
diff --git a/src/components/menu/desktop-menu/submenuComponents.js b/src/components/menu/desktop-menu/submenuComponents.js
new file mode 100644
index 0000000..a5a70c9
--- /dev/null
+++ b/src/components/menu/desktop-menu/submenuComponents.js
@@ -0,0 +1,176 @@
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import { useLocation } from 'react-router-dom'
+import { styled } from '@mui/system'
+import { Box } from '@mui/joy'
+import { Link } from '@components/link'
+import { ChevronRightIcon } from '../../icons/chevron-icons'
+
+export const SubmenuHeader = styled(Box)(({ active }) => ({
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: '0.5rem 0.75rem',
+ gap: '0.5rem',
+ fontWeight: 400,
+ cursor: 'pointer',
+ height: '100%',
+ whiteSpace: 'nowrap',
+ textTransform: 'uppercase',
+ color: active ? 'var(--knit-palette-primary-dark)' : 'var(--knit-palette-primary-900)',
+ backgroundColor: active ? '#0001' : 'transparent',
+ '&:hover': {
+ backgroundColor: '#0002',
+ },
+}))
+
+export const DropdownWrapper = styled(Box)({
+ position: 'absolute',
+ top: '100%',
+ left: 0,
+ backgroundColor: '#fff',
+ boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
+ zIndex: 2,
+ minWidth: '200px',
+ transformOrigin: 'top left',
+ transition: 'opacity 0.3s ease',
+ textAlign: 'left',
+})
+
+export const SubSubmenuWrapper = styled(Box)({
+ position: 'absolute',
+ top: 0,
+ left: '100%',
+ backgroundColor: '#fff',
+ boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
+ zIndex: 3,
+ minWidth: '200px',
+ transformOrigin: 'top left',
+ transition: 'opacity 0.3s ease',
+})
+
+export const SubMenuLink = styled(Link)({
+ padding: '0.5rem',
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ textDecoration: 'none',
+ color: 'var(--knit-palette-primary-900)',
+ textTransform: 'uppercase',
+ letterSpacing: '0.5px',
+ textAlign: 'left',
+ transition: 'background-color 250ms',
+ '& svg': {
+ marginLeft: '0.25rem'
+ },
+ '&:hover': {
+ textDecoration: 'none',
+ }
+})
+
+export const SubMenuItem = styled(Box)(({ active }) => ({
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ height: '100%',
+ backgroundColor: active ? '#0001' : 'transparent',
+ transition: 'background-color 250ms',
+ width: '100%',
+ border: '1px solid #0001',
+ '&:hover': {
+ backgroundColor: '#0002',
+ color: 'var(--knit-palette-primary-dark)',
+ },
+ '[aria-current="page"]': {
+ backgroundColor: '#0001',
+ color: 'var(--knit-palette-primary-dark)',
+ },
+}))
+
+export const Submenu = ({ items }) => {
+ const [openSubSubmenu, setOpenSubSubmenu] = useState(-1)
+ const location = useLocation()
+
+ // determine if the current path matches a submenu item
+ const isActive = (path) => location.pathname.startsWith(path)
+
+ return (
+
+ {items.map(({ label, path, external, subsubItems }, index) => {
+ const active = isActive(path) || (subsubItems && subsubItems.some((item) => isActive(item.path)))
+ return (
+ setOpenSubSubmenu(index) : undefined}
+ onMouseOut={subsubItems ? () => setOpenSubSubmenu(-1) : undefined}
+ >
+
+ {label}
+ {subsubItems && (
+
+ )}
+
+ {subsubItems && openSubSubmenu === index && (
+
+ )}
+
+ )
+ })}
+
+ )
+}
+
+export const SubSubmenu = ({ items }) => {
+ const location = useLocation()
+
+ const isActive = (path) => location.pathname.startsWith(path)
+
+ return (
+
+ {items.map(({ label, path, external }) => (
+
+
+ {label}
+
+
+ ))}
+
+ )
+}
+
+Submenu.propTypes = {
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string.isRequired,
+ external: PropTypes.bool,
+ subsubItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string.isRequired,
+ external: PropTypes.bool,
+ })
+ ),
+ })
+ ).isRequired,
+}
+
+SubSubmenu.propTypes = {
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string.isRequired,
+ external: PropTypes.bool,
+ })
+ ).isRequired,
+};
diff --git a/src/components/menu/drawer-menu.js b/src/components/menu/drawer-menu.js
index 36310cd..e8ee5b1 100644
--- a/src/components/menu/drawer-menu.js
+++ b/src/components/menu/drawer-menu.js
@@ -1,41 +1,65 @@
-import { Fragment, useState } from 'react'
-import { Box, Divider, Drawer, IconButton, ModalClose, Stack } from '@mui/joy'
-import { Menu as MenuIcon } from 'react-feather'
-import { Link } from '@components/link'
-import { menuPropTypes } from './menu'
-import numberlessKnitLogo from '@images/knit-logo-numberless-dark.png'
-
-//
+import { Fragment, useState } from 'react';
+import PropTypes from 'prop-types';
+import { Box, Divider, Drawer, IconButton, ModalClose, Stack } from '@mui/joy';
+import { Menu as MenuIcon } from 'react-feather';
+import { Link } from '@components/link';
+import numberlessKnitLogo from '@images/knit-logo-numberless-dark.png';
export const DrawerMenu = ({ options = [] }) => {
- const [open, setOpen] = useState(false)
+ const [open, setOpen] = useState(false);
+
+ // Helper function to flatten subItems and subsubItems
+ const transformMenuOptions = (menuOptions) =>
+ menuOptions.map(({ label, path, subItems = [], ...rest }) => {
+ // Flatten subItems and subsubItems, omitting "Additional KNITs"
+ if (label === 'Additional KNITs') {
+ return subItems.flatMap((sub) => sub.subsubItems || sub);
+ }
+
+ return {
+ label,
+ path,
+ subItems: subItems.flatMap((sub) => (sub.subsubItems ? sub.subsubItems : sub)),
+ ...rest,
+ };
+ });
+
+ // Render menu items recursively
+ const renderMenuItems = (items, level = 0) =>
+ items.flatMap(({ label, path, subItems = [] }) => [
+ setOpen(false)}
+ style={{ paddingLeft: `${level * 16}px` }} // Indentation for hierarchy
+ >
+ {path ? (
+
+ {label}
+
+ ) : (
+ {label}
+ )}
+ ,
+ ...renderMenuItems(subItems, level + 1), // Recursively render subItems
+ ]);
+
+ const transformedOptions = transformMenuOptions(options);
return (
setOpen(true) }
+ onClick={() => setOpen(true)}
sx={{ mx: 2 }}
>
- setOpen(false) }
- >
-
-
-
+ setOpen(false)}>
+
+
+
@@ -68,25 +92,35 @@ export const DrawerMenu = ({ options = [] }) => {
textTransform: 'uppercase',
},
'[aria-current="page"]': {
- backgroundColor: '#0001',
+ backgroundColor: '#0001',
},
}}
>
- {
- options.map(({ label, path }) => (
- setOpen(false) }
- >
- { label }
-
- ))
- }
+ {renderMenuItems(transformedOptions)}
- )
-}
+ );
+};
-DrawerMenu.propTypes = { ...menuPropTypes }
+// Explicitly define prop-types
+DrawerMenu.propTypes = {
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string, // Can be undefined for non-clickable menu items
+ subItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string,
+ subsubItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string,
+ })
+ ),
+ })
+ ),
+ })
+ ),
+};
diff --git a/src/components/menu/menu.js b/src/components/menu/menu.js
index b9c5bca..5dcbc3f 100644
--- a/src/components/menu/menu.js
+++ b/src/components/menu/menu.js
@@ -19,12 +19,11 @@ export const Menu = ({ options = [] }) => {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
- height: '60px',
border: 'solid var(--knit-palette-primary-700)',
borderWidth: '1px 0',
position: 'sticky',
top: 0,
- overflow: 'hidden',
+ overflow: 'visible',
zIndex: 9,
}}
>
@@ -43,8 +42,24 @@ export const Menu = ({ options = [] }) => {
export const menuPropTypes = PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
- path: PropTypes.string.isRequired,
+ path: PropTypes.string,
+ view: PropTypes.element,
+ external: PropTypes.bool,
+ subItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string,
+ external: PropTypes.bool,
+ subsubItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ path: PropTypes.string,
+ external: PropTypes.bool,
+ })
+ ),
+ })
+ ),
}).isRequired,
-).isRequired
+).isRequired;
-Menu.propTypes = { ...menuPropTypes }
+Menu.propTypes = { options: menuPropTypes };
diff --git a/src/content/cfp.mdx b/src/content/cfp.mdx
index 2d16fad..331b542 100644
--- a/src/content/cfp.mdx
+++ b/src/content/cfp.mdx
@@ -2,7 +2,7 @@ import { Button } from '../components/button'
# Calls for Action
-## Calls for Demos
+## Calls for Demos
Showcase your innovative projects at KNIT10's Demo Night! Share the work you've been developing by filling out the form to be considered for a presentation.
diff --git a/src/content/past-knits/knit9.mdx b/src/content/past-knits/knit9.mdx
new file mode 100644
index 0000000..5b57399
--- /dev/null
+++ b/src/content/past-knits/knit9.mdx
@@ -0,0 +1,35 @@
+import { Button } from '@components/button'
+import knit9logo from '@images/KNIT9-logo.png'
+
+# KNIT 9 | September 24, 2024
+
+## Event Information
+KNIT 9, the next FABRIC Community Workshop, took place September 24, 2024 in Kansas City, MO, and will be co-located with the MERIF Workshop. The workshop will include small-group, hands-on FABRIC tutorial sessions and advanced training topics. During the event, experimenters across multiple science domains will highlight their use of FABRIC to push forward compelling experiments. The FABRIC team will set the stage for the future of FABRIC and solicit feedback from fellow participants during talks and open mic sessions.
+> UMKC Student Union
+
+> 5100 Cherry Street, Kansas City, MO 64110
+
+## Speaker Directory
+To learn more about the presenters at KNIT9, follow the link below for the Speaker Directory.
+
+
+
+
+
+## Information Packet
+
+The KNIT 9 Information Packet serves as your one-stop-shop for all links and materials you will need for the workshop.
+
+
+
+
+
+## Agenda
+
+
+
+
+
+##### Funding Details
+
+FABRIC is funded by NSF grants CNS-1935966, CNS-2029176, CNS-2029200, CNS-2029235, CNS-2029260, CNS-2029261 and CNS-2330891.
\ No newline at end of file
diff --git a/src/views/index.js b/src/views/index.js
index 0cc9a65..d5bb1e7 100644
--- a/src/views/index.js
+++ b/src/views/index.js
@@ -4,5 +4,5 @@ export * from './home'
export * from './not-found'
export * from './registration'
export * from './travel'
-
+export * from './past-knits'
export * from './__markdown'
diff --git a/src/views/past-knits/index.js b/src/views/past-knits/index.js
new file mode 100644
index 0000000..ced5f94
--- /dev/null
+++ b/src/views/past-knits/index.js
@@ -0,0 +1 @@
+export * from './knit9'
\ No newline at end of file
diff --git a/src/views/past-knits/knit9.js b/src/views/past-knits/knit9.js
new file mode 100644
index 0000000..a322bfd
--- /dev/null
+++ b/src/views/past-knits/knit9.js
@@ -0,0 +1,15 @@
+import KNIT9 from '@content/past-knits/knit9.mdx'
+
+import { Page } from '@components/layout'
+import { componentMap } from "@components/markdown"
+
+export const Knit9View = () => {
+ return (
+
+
+
+ )
+}
\ No newline at end of file