Skip to content

Commit

Permalink
Merge pull request #205 from assemblee-virtuelle/200-TopMenuLayout
Browse files Browse the repository at this point in the history
[Minor] 200 - Add "topMenu" Layout
  • Loading branch information
mguihal authored Nov 10, 2024
2 parents 73a0d85 + 99e85f1 commit dc082bc
Show file tree
Hide file tree
Showing 30 changed files with 568 additions and 47 deletions.
6 changes: 4 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Admin, Resource, memoryStore } from 'react-admin';
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { LoginPage } from '@semapps/auth-provider';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient } from 'react-query';
Expand All @@ -14,7 +15,7 @@ import theme from './config/theme';
import resources from './resources';

import { Layout } from './common/layout';
import { LayoutProvider } from './layouts/LayoutContext';
import { LayoutProvider } from './layouts/LayoutProvider';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -28,10 +29,11 @@ const App = () => (
<StyledEngineProvider injectFirst>
<BrowserRouter>
<ThemeProvider theme={theme}>
<CssBaseline />
<LayoutProvider layoutOptions={config.layout}>
<Admin
disableTelemetry
title="Archipel"
title={config.title}
authProvider={authProvider}
dataProvider={dataProvider}
i18nProvider={i18nProvider}
Expand Down
25 changes: 23 additions & 2 deletions frontend/src/common/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import React from 'react';
import React, { Suspense } from 'react';
import { LayoutProps } from 'react-admin';
import { Box, CircularProgress } from '@mui/material';
import { styled } from '@mui/material/styles';
import { useLayoutContext } from '../../layouts/LayoutContext';

const FullPageBox = styled(Box)(() => ({
width: '100vw',
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}));

const BaseLayout = (props: LayoutProps) => {
const { Layout } = useLayoutContext();
return <Layout {...props} />;

return (
<Suspense
fallback={
<FullPageBox>
<CircularProgress />
</FullPageBox>
}
>
<Layout {...props} />
</Suspense>
);
};

export default BaseLayout;
27 changes: 27 additions & 0 deletions frontend/src/common/list/MobileMapPopupContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { ShowButton, EditButton, useResourceDefinition, useRecordContext } from 'react-admin';
import { Box, Typography } from '@mui/material';
import { useLayoutContext } from '../../layouts/LayoutContext';

const MobileMapPopupContent = () => {
const record = useRecordContext();
const resourceDefinition = useResourceDefinition({});
const layout = useLayoutContext();

if (!record) return null;

return (
<Box sx={{ paddingBottom: layout.name === 'topMenu' ? '56px' : 0 }}>
{record.label && <Typography variant="h5">{record.label}</Typography>}
{record.description && (
<Typography>
{record.description.length > 150 ? `${record.description.substring(0, 150)}...` : record.description}
</Typography>
)}
{resourceDefinition.hasShow && <ShowButton />}
{resourceDefinition.hasEdit && <EditButton />}
</Box>
);
};

export default MobileMapPopupContent;
15 changes: 14 additions & 1 deletion frontend/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
const config = {
import { LayoutOptions } from "../layouts/LayoutContext";

interface ConfigInterface {
middlewareUrl: string;
mapboxAccessToken: string;
importableResources: string[];
title: string;
layout: LayoutOptions;
}

const config: ConfigInterface = {
// Middleware API url (ex: https://<host>:<port>/). Should contain a trailing slash.
middlewareUrl: import.meta.env.VITE_MIDDLEWARE_URL,

Expand All @@ -17,6 +27,9 @@ const config = {
"Skill",
],

// Application title
title: 'Archipelago',

// UI layout configuration
layout: {
name: 'leftMenu',
Expand Down
18 changes: 0 additions & 18 deletions frontend/src/index.css

This file was deleted.

1 change: 0 additions & 1 deletion frontend/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';

const root = createRoot(
Expand Down
28 changes: 6 additions & 22 deletions frontend/src/layouts/LayoutContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { createContext, FunctionComponent, PropsWithChildren, ReactNode, useContext } from 'react';
import { createContext, FunctionComponent, PropsWithChildren, ReactNode, useContext } from 'react';
import { LayoutProps } from 'react-admin';
import leftMenu, { LayoutOptions as LeftMenuOptions } from './leftMenu';
import { LayoutOptions as LeftMenuOptions } from './leftMenu';
import { LayoutOptions as TopMenuOptions } from './topMenu';

type BaseViewProps = PropsWithChildren<{
title: string | ReactNode;
Expand All @@ -15,30 +16,13 @@ export type LayoutComponents = {
};

export type LeftMenuLayoutType = LayoutComponents & LeftMenuOptions;
export type TopMenuLayoutType = LayoutComponents & TopMenuOptions;

type LayoutOptions = LeftMenuOptions;
type LayoutContextType = LeftMenuLayoutType;
export type LayoutOptions = LeftMenuOptions | TopMenuOptions;
type LayoutContextType = LeftMenuLayoutType | TopMenuLayoutType;

export const LayoutContext = createContext<LayoutContextType | undefined>(undefined);

const layoutsComponents = {
leftMenu,
};

type LayoutProviderProps = PropsWithChildren<{
layoutOptions: LayoutOptions;
}>;

export const LayoutProvider = ({ children, layoutOptions }: LayoutProviderProps) => {
const layoutComponents = layoutsComponents[layoutOptions.name];

if (!layoutComponents) {
return null;
}

return <LayoutContext.Provider value={{ ...layoutOptions, ...layoutComponents }}>{children}</LayoutContext.Provider>;
};

export const useLayoutContext = <T extends LayoutOptions = LayoutOptions>() => {
const layout = useContext(LayoutContext);
return layout as T & LayoutComponents;
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/layouts/LayoutProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { PropsWithChildren } from 'react';
import { LayoutContext, LayoutOptions } from './LayoutContext';

import leftMenu from './leftMenu';
import topMenu from './topMenu';

const layoutsComponents = {
leftMenu,
topMenu,
};

type LayoutProviderProps = PropsWithChildren<{
layoutOptions: LayoutOptions;
}>;

export const LayoutProvider = ({ children, layoutOptions }: LayoutProviderProps) => {
const layoutComponents = layoutsComponents[layoutOptions.name];

if (!layoutComponents) {
return null;
}

return <LayoutContext.Provider value={{ ...layoutOptions, ...layoutComponents }}>{children}</LayoutContext.Provider>;
};
80 changes: 80 additions & 0 deletions frontend/src/layouts/topMenu/AppBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { AppBar as MuiAppBar, Box, Button, Stack, Toolbar, Typography, Link } from '@mui/material';
import { styled } from '@mui/material/styles';
import { Link as RRLink } from 'react-router-dom';
import config from '../../config/config';
import UserMenu from './UserMenu';
import ResourcesMenu from './ResourcesMenu';
import { useLayoutContext } from '../LayoutContext';
import { LayoutOptions } from '.';

const AppTitle = styled(Typography)(({ theme }) => ({
textWrap: 'balance',
lineHeight: '1.1',
[theme.breakpoints.down('lg')]: {
fontSize: '1rem',
textAlign: 'center',
},
})) as typeof Typography;

const AppBar = () => {
const layout = useLayoutContext<LayoutOptions>();
const logo = layout.options.logo;

return (
<MuiAppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}>
<Toolbar>
{logo && (
<Box sx={{ marginRight: { xs: 0, lg: 1 } }}>
<Link component={RRLink} to="/" color="inherit" underline="none">
<Box
height={'48px'}
component={'img'}
src={typeof logo === 'string' ? logo : logo.url}
alt={typeof logo === 'string' ? 'Logo' : logo.alt}
sx={{ paddingTop: '5px', boxSizing: 'content-box' }}
/>
</Link>
</Box>
)}

{!(layout.options.title === false) && (
<>
{(typeof layout.options.title === 'function' && layout.options.title?.()) || (
<AppTitle component="h1" variant="h6">
<Link component={RRLink} to="/" color="inherit" underline="none">
{config.title}
</Link>
</AppTitle>
)}
</>
)}

<Box sx={{ flexGrow: 1 }} />

<Stack sx={{ display: { xs: 'none', md: 'block' } }} spacing={2} direction="row">
{(layout.options.mainMenu || []).map((item) => (
<Button
key={item.label}
color="inherit"
size="large"
startIcon={item.icon ? <item.icon /> : undefined}
component={RRLink}
to={item.link}
>
{item.label}
</Button>
))}
</Stack>

<Box sx={{ flexGrow: 1 }} />

<ResourcesMenu />

<UserMenu />
</Toolbar>
</MuiAppBar>
);
};

export default AppBar;
65 changes: 65 additions & 0 deletions frontend/src/layouts/topMenu/Aside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { PropsWithChildren, useState } from 'react';
import { Box, Drawer, Fab, Toolbar, useMediaQuery } from '@mui/material';
import { styled, useTheme } from '@mui/material/styles';
import TuneIcon from '@mui/icons-material/Tune';
import { useLayoutContext } from '../LayoutContext';
import { LayoutOptions } from '.';

const asideWidth = '300px';

const ContentBox = styled(Box)(({ theme }) => ({
minWidth: asideWidth,
padding: 16,
boxSizing: 'border-box',
[theme.breakpoints.down('md')]: {
paddingBottom: 56 + 16,
},
}));

const Aside = ({ children }: PropsWithChildren) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const [isAsideOpen, setAsideOpen] = useState(false);

const layout = useLayoutContext<LayoutOptions>();
const side = layout.options.sideBarPlacement;

return (
<>
{isMobile && (
<Fab
variant="extended"
color="primary"
sx={{
position: 'fixed',
bottom: 64,
left: 10,
}}
onClick={() => setAsideOpen(true)}
>
<TuneIcon sx={{ mr: 1 }} />
Filtres
</Fab>
)}
<Drawer
sx={{
width: asideWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: asideWidth,
boxSizing: 'border-box',
},
}}
variant={isMobile ? 'temporary' : 'permanent'}
open={isAsideOpen}
anchor={side}
onClose={() => setAsideOpen(false)}
>
<Toolbar />
<ContentBox>{children}</ContentBox>
</Drawer>
</>
);
};

export default Aside;
Loading

0 comments on commit dc082bc

Please sign in to comment.