diff --git a/packages/web/package.json b/packages/web/package.json index da95eb5f..b29b032a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -6,6 +6,7 @@ "@apollo/client": "^3.4.15", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", + "@mui/icons-material": "^5.0.1", "@mui/material": "^5.0.2", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", diff --git a/packages/web/src/components/App/index.tsx b/packages/web/src/components/App/index.tsx index 5128d756..449db383 100644 --- a/packages/web/src/components/App/index.tsx +++ b/packages/web/src/components/App/index.tsx @@ -1,10 +1,10 @@ -import React from 'react'; import { FormattedMessage } from 'react-intl'; +import Layout from 'components/Layout'; -function App() { +export default function App() { return ( - + + + ); } - -export default App; diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx new file mode 100644 index 00000000..824117fe --- /dev/null +++ b/packages/web/src/components/AppBar/index.tsx @@ -0,0 +1,62 @@ +import MuiAppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import MenuIcon from '@mui/icons-material/Menu'; +import SearchIcon from '@mui/icons-material/Search'; + +import HideOnScroll from 'components/HideOnScroll'; +import { FormattedMessage } from 'react-intl'; +import useFormatMessage from 'hooks/useFormatMessage'; +import { Search, SearchIconWrapper, InputBase } from './style'; + +type AppBarProps = { + onMenuClick: () => void; +}; + +export default function AppBar({ onMenuClick }: AppBarProps) { + const formatMessage = useFormatMessage(); + + return ( + + + theme.zIndex.drawer + 1 }}> + + + {/* TODO: make Drawer in Layout togglable. */} + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/web/src/components/AppBar/style.ts b/packages/web/src/components/AppBar/style.ts new file mode 100644 index 00000000..3cd5bd23 --- /dev/null +++ b/packages/web/src/components/AppBar/style.ts @@ -0,0 +1,44 @@ +import { styled, alpha } from '@mui/material/styles'; +import MuiInputBase from '@mui/material/InputBase'; + +export const Search = styled('div')(({ theme }) => ({ + position: 'relative', + borderRadius: theme.shape.borderRadius, + backgroundColor: alpha(theme.palette.common.white, 0.15), + '&:hover': { + backgroundColor: alpha(theme.palette.common.white, 0.25), + }, + marginLeft: 0, + width: '100%', + [theme.breakpoints.up('sm')]: { + marginLeft: theme.spacing(1), + width: 'auto', + }, +})); + +export const SearchIconWrapper = styled('div')(({ theme }) => ({ + padding: theme.spacing(0, 2), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +export const InputBase = styled(MuiInputBase)(({ theme }) => ({ + color: 'inherit', + '& .MuiInputBase-input': { + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)})`, + transition: theme.transitions.create('width'), + width: '100%', + [theme.breakpoints.up('sm')]: { + width: '12ch', + '&:focus': { + width: '20ch', + }, + }, + }, +})); \ No newline at end of file diff --git a/packages/web/src/components/Drawer/index.tsx b/packages/web/src/components/Drawer/index.tsx new file mode 100644 index 00000000..98d428df --- /dev/null +++ b/packages/web/src/components/Drawer/index.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import { DrawerProps } from '@mui/material/Drawer'; +import Toolbar from '@mui/material/Toolbar'; +import List from '@mui/material/List'; +import Divider from '@mui/material/Divider'; +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import InboxIcon from '@mui/icons-material/MoveToInbox'; +import MailIcon from '@mui/icons-material/Mail'; + +import HideOnScroll from 'components/HideOnScroll'; +import { Drawer as BaseDrawer } from './style'; + +export default function Drawer(props: DrawerProps) { + return ( + + + + + + {['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => ( + + + {index % 2 === 0 ? : } + + + + ))} + + + + {['All mail', 'Trash', 'Spam'].map((text, index) => ( + + + {index % 2 === 0 ? : } + + + + ))} + + + ); +} diff --git a/packages/web/src/components/Drawer/style.ts b/packages/web/src/components/Drawer/style.ts new file mode 100644 index 00000000..6bd47537 --- /dev/null +++ b/packages/web/src/components/Drawer/style.ts @@ -0,0 +1,42 @@ +import { styled, Theme, CSSObject } from '@mui/material/styles'; +import MuiDrawer from '@mui/material/Drawer'; + +const drawerWidth = 240; + +const openedMixin = (theme: Theme): CSSObject => ({ + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: 'hidden', +}); + +const closedMixin = (theme: Theme): CSSObject => ({ + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: 'hidden', + width: `calc(${theme.spacing(7)} + 1px)`, + [theme.breakpoints.up('sm')]: { + width: `calc(${theme.spacing(9)} + 1px)`, + }, +}); + +export const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( + ({ theme, open }) => ({ + width: drawerWidth, + flexShrink: 0, + whiteSpace: 'nowrap', + boxSizing: 'border-box', + ...(open && { + ...openedMixin(theme), + '& .MuiDrawer-paper': openedMixin(theme), + }), + ...(!open && { + ...closedMixin(theme), + '& .MuiDrawer-paper': closedMixin(theme), + }), + }), +); diff --git a/packages/web/src/components/HideOnScroll/index.tsx b/packages/web/src/components/HideOnScroll/index.tsx new file mode 100644 index 00000000..c6723dd1 --- /dev/null +++ b/packages/web/src/components/HideOnScroll/index.tsx @@ -0,0 +1,10 @@ +import Slide, { SlideProps } from '@mui/material/Slide'; +import useScrollTrigger from '@mui/material/useScrollTrigger'; + +export default function HideOnScroll(props: SlideProps) { + const trigger = useScrollTrigger(); + + return ( + + ); +}; diff --git a/packages/web/src/components/Layout/index.tsx b/packages/web/src/components/Layout/index.tsx new file mode 100644 index 00000000..455746dc --- /dev/null +++ b/packages/web/src/components/Layout/index.tsx @@ -0,0 +1,30 @@ +import { useState, useCallback } from 'react'; +import Box from '@mui/material/Box'; +import AppBar from 'components/AppBar'; +import Drawer from 'components/Drawer'; +import Toolbar from '@mui/material/Toolbar'; + +type LayoutProps = { + children: React.ReactNode; +} + +export default function Layout({ children }: LayoutProps) { + const [isDrawerOpen, setDrawerOpen] = useState(false); + const onMenuClick = useCallback(() => { setDrawerOpen(value => !value) }, []); + + return ( + <> + + + + + + + + + {children} + + + + ); +} diff --git a/packages/web/src/hooks/useFormatMessage.tsx b/packages/web/src/hooks/useFormatMessage.tsx new file mode 100644 index 00000000..c999941a --- /dev/null +++ b/packages/web/src/hooks/useFormatMessage.tsx @@ -0,0 +1,11 @@ +import { useIntl } from 'react-intl'; + +type Values = { + [key: string]: any, +} + +export default function useFormatMessage() { + const { formatMessage } = useIntl(); + + return (id: string, values: Values = {}) => formatMessage({ id }, values); +} diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index dce38dd0..9eb806a7 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -1,3 +1,5 @@ { - "welcomeText": "Hello world!" + "brandText": "automatisch", + "searchPlaceholder": "Search...", + "welcomeText": "Here comes the dashboard homepage." } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 33d37f4d..ad1d8fae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2386,6 +2386,13 @@ prop-types "^15.7.2" react-is "^17.0.2" +"@mui/icons-material@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.0.1.tgz#fb7ffeba0b3604aab4a9b91644d2fc1aabb3b4f1" + integrity sha512-AZehR/Uvi9VodsNPk9ae1lENKrf1evqx9suiP6VIqu7NxjZOlw/m/yA2gRAMmLEmIGr7EChfi/wcXuq6BpM9vw== + dependencies: + "@babel/runtime" "^7.15.4" + "@mui/material@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.0.2.tgz#380cf0ef42c538a68158b4da19c317178b22d10f"