diff --git a/packages/tupaia-web/.storybook/ReactRouterDecorator.tsx b/packages/tupaia-web/.storybook/ReactRouterDecorator.tsx new file mode 100644 index 0000000000..5d640ff82c --- /dev/null +++ b/packages/tupaia-web/.storybook/ReactRouterDecorator.tsx @@ -0,0 +1,27 @@ +import React, { useEffect } from 'react'; +import { action } from '@storybook/addon-actions'; +import { BrowserRouter, useLocation } from 'react-router-dom'; +import { Args } from '@storybook/react'; +import { DecoratorFunction } from '@storybook/csf'; + +const LocationChangeAction = ({ children }) => { + const location = useLocation(); + + useEffect(() => { + if (location.key !== 'default') action('React Router Location Change')(location); + }, [location]); + + return <>{children}; +}; + +const ReactRouterDecorator: DecoratorFunction = (Story, context) => { + return ( + + + + + + ); +}; + +export default ReactRouterDecorator; diff --git a/packages/tupaia-web/.storybook/main.ts b/packages/tupaia-web/.storybook/main.ts index bcd781cba5..96194a5be8 100644 --- a/packages/tupaia-web/.storybook/main.ts +++ b/packages/tupaia-web/.storybook/main.ts @@ -1,6 +1,6 @@ import type { StorybookConfig } from '@storybook/react-vite'; const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', diff --git a/packages/tupaia-web/.storybook/preview.tsx b/packages/tupaia-web/.storybook/preview.tsx index 763b4020eb..660fc72347 100644 --- a/packages/tupaia-web/.storybook/preview.tsx +++ b/packages/tupaia-web/.storybook/preview.tsx @@ -1,15 +1,14 @@ import React from 'react'; import type { Preview } from '@storybook/react'; import { AppStyleProviders } from '../src/AppStyleProviders'; -import { DARK_BLUE, WHITE } from '../src/theme'; - +import ReactRouterDecorator from './ReactRouterDecorator'; const preview: Preview = { parameters: { backgrounds: { default: 'Dark', values: [ - { name: 'Dark', value: DARK_BLUE }, - { name: 'Light', value: WHITE }, + { name: 'Dark', value: '#135D8F' }, + { name: 'Light', value: '#ffffff' }, ], }, actions: { argTypesRegex: '^on[A-Z].*' }, @@ -21,6 +20,7 @@ const preview: Preview = { }, }, decorators: [ + ReactRouterDecorator, Story => { return ( diff --git a/packages/tupaia-web/package.json b/packages/tupaia-web/package.json index 8518eb5471..568b1360c1 100644 --- a/packages/tupaia-web/package.json +++ b/packages/tupaia-web/package.json @@ -40,7 +40,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.3.0", - "react-router-dom": "6.3.0", + "react-router-dom": "6.3.0", "styled-components": "^5.1.0", "vite-plugin-ejs": "^1.6.4", "vite-plugin-env-compatible": "^1.1.1" diff --git a/packages/tupaia-web/public/images/tupaia-logo-light.svg b/packages/tupaia-web/public/images/tupaia-logo-light.svg new file mode 100644 index 0000000000..9a15c95936 --- /dev/null +++ b/packages/tupaia-web/public/images/tupaia-logo-light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/tupaia-web/src/App.tsx b/packages/tupaia-web/src/App.tsx index 0ff12be8d2..e66328215e 100644 --- a/packages/tupaia-web/src/App.tsx +++ b/packages/tupaia-web/src/App.tsx @@ -4,12 +4,19 @@ */ import React from 'react'; import { AppStyleProviders } from './AppStyleProviders'; -import { Router } from './Router'; +import { Routes } from './Routes'; +import { Layout } from './layout'; +import { BrowserRouter } from 'react-router-dom'; const App = () => { return ( - + + {/** The Layout component needs to be inside BrowserRouter so that Link component from react-router-dom can be used (in menu etc.) */} + + + + ); }; diff --git a/packages/tupaia-web/src/AppStyleProviders.tsx b/packages/tupaia-web/src/AppStyleProviders.tsx index 35900ac04f..0c223b8085 100644 --- a/packages/tupaia-web/src/AppStyleProviders.tsx +++ b/packages/tupaia-web/src/AppStyleProviders.tsx @@ -3,29 +3,10 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React, { ReactNode } from 'react'; -import { - StylesProvider, - createMuiTheme, - ThemeProvider as MuiThemeProvider, -} from '@material-ui/core/styles'; +import { ThemeProvider as MuiThemeProvider, StylesProvider } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; import { ThemeProvider } from 'styled-components'; - -// Base theme overrides. Any extra overrides should be added here, as needed. -const theme = createMuiTheme({ - palette: { - type: 'dark', - primary: { - main: '#0296c5', // Main blue (as seen on primary buttons) - }, - secondary: { - main: '##ee6230', // Tupaia Orange - }, - background: { - default: '#262834', // Dark blue background - }, - }, -}); +import { theme } from './theme'; export const AppStyleProviders = ({ children }: { children: ReactNode }) => ( diff --git a/packages/tupaia-web/src/Router.tsx b/packages/tupaia-web/src/Router.tsx deleted file mode 100644 index 319fcecffb..0000000000 --- a/packages/tupaia-web/src/Router.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Tupaia - * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd - */ -import React from 'react'; -import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; -import { - LandingPage, - LoginForm, - PasswordResetForm, - Project, - RegisterForm, - RequestAccessForm, - VerifyEmailForm, -} from './pages'; -import { DEFAULT_URL } from './constants'; - -/** - * This Router is using [version 6.3]{@link https://reactrouter.com/en/v6.3.0}, as later versions are not supported by our TS setup. See [this issue here]{@link https://github.com/remix-run/react-router/discussions/8364} - * This means the newer 'createBrowserRouter' and 'RouterProvider' can't be used here. - * - * **/ - -export const Router = () => ( - - - } /> - {/** - * The below user pages will actually be modals, which will be done when each view is created. There is an example at: https://github.com/remix-run/react-router/tree/dev/examples/modal - */} - } /> - } /> - } /> - } /> - } /> - } /> - {/** Because react-router v 6.3 doesn't support optional url segments, we need to handle dashboardCode with a splat/catch-all instead */} - } /> - - -); diff --git a/packages/tupaia-web/src/Routes.tsx b/packages/tupaia-web/src/Routes.tsx new file mode 100644 index 0000000000..e16b719442 --- /dev/null +++ b/packages/tupaia-web/src/Routes.tsx @@ -0,0 +1,36 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import React from 'react'; +import { Navigate, Route, Routes as RouterRoutes } from 'react-router-dom'; +import { + LandingPage, + LoginForm, + PasswordResetForm, + Project, + RegisterForm, + RequestAccessForm, + VerifyEmailForm, +} from './pages'; +import { DEFAULT_URL } from './constants'; + +/** + * This Router is using [version 6.3]{@link https://reactrouter.com/en/v6.3.0}, as later versions are not supported by our TS setup. See [this issue here]{@link https://github.com/remix-run/react-router/discussions/8364} + * This means the newer 'createBrowserRouter' and 'RouterProvider' can't be used here. + * + * **/ + +export const Routes = () => ( + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/** Because react-router v 6.3 doesn't support optional url segments, we need to handle dashboardCode with a splat/catch-all instead */} + } /> + +); diff --git a/packages/tupaia-web/src/components/Modal.tsx b/packages/tupaia-web/src/components/Modal.tsx new file mode 100644 index 0000000000..c40375c58b --- /dev/null +++ b/packages/tupaia-web/src/components/Modal.tsx @@ -0,0 +1,61 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import React, { ReactNode } from 'react'; +import { Dialog, Paper as MuiPaper, useTheme, useMediaQuery } from '@material-ui/core'; +import MuiCloseIcon from '@material-ui/icons/Close'; +import styled from 'styled-components'; +import { IconButton } from '@tupaia/ui-components'; + +interface ModalProps { + children?: ReactNode; + onClose: () => void; + isOpen: boolean; +} + +const Wrapper = styled.div` + text-align: center; + overflow-x: hidden; + padding: 2em; +`; + +const CloseIcon = styled(MuiCloseIcon)` + width: 1.2em; + height: 1.2em; +`; + +const CloseButton = styled(IconButton)` + background-color: transparent; + min-width: initial; + position: absolute; + top: 0.6em; + right: 0.6em; +`; + +const Paper = styled(MuiPaper)` + background-color: ${({ theme }) => theme.palette.background.default}; + padding: 0; + color: rgba(255, 255, 255, 0.9); + overflow-y: auto; + max-width: 920px; + min-width: 300px; + // Prevent width from animating. + transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms; +`; + +export const Modal = ({ children, isOpen, onClose }: ModalProps) => { + // make the modal full screen at small screen sizes + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('xs')); + return ( + + + + + + {children} + + + ); +}; diff --git a/packages/tupaia-web/src/components/index.ts b/packages/tupaia-web/src/components/index.ts index 652a972fc4..e5c2d245d9 100644 --- a/packages/tupaia-web/src/components/index.ts +++ b/packages/tupaia-web/src/components/index.ts @@ -2,3 +2,4 @@ * Tupaia * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ +export { Modal } from './Modal'; diff --git a/packages/tupaia-web/src/constants/colors.ts b/packages/tupaia-web/src/constants/colors.ts new file mode 100644 index 0000000000..2f8cb75b56 --- /dev/null +++ b/packages/tupaia-web/src/constants/colors.ts @@ -0,0 +1,5 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2021 Beyond Essential Systems Pty Ltd + */ +export const TRANSPARENT_BLACK = 'rgba(43, 45, 56, 0.94)'; diff --git a/packages/tupaia-web/src/constants/constants.ts b/packages/tupaia-web/src/constants/constants.ts index 308a6ed433..78ca63eb92 100644 --- a/packages/tupaia-web/src/constants/constants.ts +++ b/packages/tupaia-web/src/constants/constants.ts @@ -6,3 +6,5 @@ export const DEFAULT_PROJECT_CODE = 'explore'; export const DEFAULT_ENTITY_CODE = 'explore'; export const DEFAULT_URL = `${DEFAULT_PROJECT_CODE}/${DEFAULT_ENTITY_CODE}`; + +export const TUPAIA_LIGHT_LOGO_SRC = '/images/tupaia-logo-light.svg'; diff --git a/packages/tupaia-web/src/constants/index.ts b/packages/tupaia-web/src/constants/index.ts index c89172dfac..ec91fa77a7 100644 --- a/packages/tupaia-web/src/constants/index.ts +++ b/packages/tupaia-web/src/constants/index.ts @@ -2,4 +2,5 @@ * Tupaia * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ +export * from './colors'; export * from './constants'; diff --git a/packages/tupaia-web/src/layout/AuthModal.tsx b/packages/tupaia-web/src/layout/AuthModal.tsx new file mode 100644 index 0000000000..b19385f134 --- /dev/null +++ b/packages/tupaia-web/src/layout/AuthModal.tsx @@ -0,0 +1,100 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { ReactNode } from 'react'; +import { To, useNavigate } from 'react-router'; +import { Modal } from '../components'; +import styled from 'styled-components'; +import { TUPAIA_LIGHT_LOGO_SRC } from '../constants'; +import { Button, Typography } from '@material-ui/core'; +import { FONT_SIZES } from '../theme'; +import { OutlinedButton } from '@tupaia/ui-components'; + +const Logo = styled.img` + min-width: 110px; + margin-top: 1.8em; + margin-bottom: 3em; +`; + +const Title = styled(Typography)` + font-size: ${FONT_SIZES.viewTitle}; + font-weight: 500; +`; + +const Subtitle = styled(Typography)` + font-size: 1em; + margin-top: 1.4em; +`; + +const PrimaryButton = styled(Button).attrs({ + variant: 'contained', + color: 'primary', +})` + text-transform: none; + font-size: 1em; + width: 100%; + margin-left: 0 !important; + margin-top: 2em; +`; + +const SecondaryButton = styled(OutlinedButton).attrs({ + color: 'default', +})` + text-transform: none; + font-size: 1em; + width: 100%; + margin-left: 0 !important; + padding: 0.375em 1em; // to match the height of the primary button + border-color: ${({ theme }) => theme.palette.text.secondary}; + ${PrimaryButton} + & { + margin-top: 1.3em; + } +`; + +type ButtonProps = { + onClick: () => void; + text: string; +}; +interface AuthModalProps { + children?: ReactNode; + onClose?: () => void; + title?: string; + subtitle?: string; + navigateTo?: To | number; // the path to navigate to on close, if set. Numbers are acceptable for e.g. -1 meaning back 1 route + primaryButton?: ButtonProps; + secondaryButton?: ButtonProps; +} + +export const AuthModal = ({ + children, + onClose, + navigateTo = -1, + title, + subtitle, + primaryButton, + secondaryButton, +}: AuthModalProps) => { + const navigate = useNavigate(); + const onCloseModal = () => { + // navigate back to the previous route on close if no path is provided + navigate(navigateTo as To); + if (onClose) onClose(); + }; + // This modal will always be open in this case, because these are only visible on certain routes + return ( + + + {title} + {subtitle && {subtitle}} + {children} + {primaryButton && ( + {primaryButton.text} + )} + {secondaryButton && ( + {secondaryButton.text} + )} + + ); +}; diff --git a/packages/tupaia-web/src/layout/Layout.tsx b/packages/tupaia-web/src/layout/Layout.tsx new file mode 100644 index 0000000000..b7f4d896ac --- /dev/null +++ b/packages/tupaia-web/src/layout/Layout.tsx @@ -0,0 +1,39 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { ReactNode } from 'react'; +import styled from 'styled-components'; +import { EnvBanner } from '@tupaia/ui-components'; +import { TopBar } from './TopBar'; + +/** + * This is the layout for the entire app, which contains the top bar and the main content. This is used to wrap the entire app content + */ +const Container = styled.div` + position: fixed; + flex-direction: column; + flex-wrap: nowrap; + width: 100%; + pointer-events: none; + display: flex; + align-items: stretch; + align-content: stretch; + overflow-y: hidden; + height: 100%; + + svg.recharts-surface { + overflow: visible; + } +`; + +export const Layout = ({ children }: { children: ReactNode }) => { + return ( + + + + {children} + + ); +}; diff --git a/packages/tupaia-web/src/layout/MapLayout/MapLayout.tsx b/packages/tupaia-web/src/layout/MapLayout/MapLayout.tsx new file mode 100644 index 0000000000..e37ec61001 --- /dev/null +++ b/packages/tupaia-web/src/layout/MapLayout/MapLayout.tsx @@ -0,0 +1,71 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React from 'react'; +import styled from 'styled-components'; +import { MapWatermark } from './MapWatermark'; + +const MapContainer = styled.div` + height: 100vh; + transition: width 0.5s ease; + width: 100%; +`; + +export const Map = () => { + return {/* */}; +}; + +const Wrapper = styled.div` + flex: 1; + display: flex; + position: relative; +`; + +const MapControlsContainer = styled.div` + display: flex; + flex-direction: column; + flex: 1; + position: absolute; // make this absolutely positioned so that it lays over the map + width: 100%; + height: 100%; +`; + +const MapLegendWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-end; + justify-content: center; +`; + +const Watermark = styled(MapWatermark)` + margin-left: 2px; + margin-bottom: 16px; +`; + +// Placeholder for MapOverlaySelector component +const MapOverlaySelector = styled.div` + width: 25%; + margin: 2em; + height: 200px; + background-color: rgba(255, 255, 255, 0.2); +`; + +/** + * This is the layout for the lefthand side of the app, which contains the map controls and watermark, as well as the map + */ + +export const MapLayout = () => { + return ( + + + + {/** This is where the map legend would go */} + + + {/** This is where the tilepicker would go */} + + + ); +}; diff --git a/packages/tupaia-web/src/layout/MapLayout/MapWatermark.tsx b/packages/tupaia-web/src/layout/MapLayout/MapWatermark.tsx new file mode 100644 index 0000000000..931c2c32d3 --- /dev/null +++ b/packages/tupaia-web/src/layout/MapLayout/MapWatermark.tsx @@ -0,0 +1,38 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd + */ + +import React, { AnchorHTMLAttributes } from 'react'; +import styled from 'styled-components'; + +const StyledLink = styled.a` + position: absolute; + display: block; + height: 20px; + width: 65px; + left: 0; + bottom: 0; + text-indent: -9999px; + z-index: 99999; + overflow: hidden; + + /* This is the recommended way of adding the watermark + @see https://docs.mapbox.com/help/how-mapbox-works/attribution */ + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgODAuNDcgMjAuMDIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDgwLjQ3IDIwLjAyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe29wYWNpdHk6MC42O2ZpbGw6I0ZGRkZGRjtlbmFibGUtYmFja2dyb3VuZDpuZXcgICAgO30uc3Qxe29wYWNpdHk6MC42O2VuYWJsZS1iYWNrZ3JvdW5kOm5ldyAgICA7fTwvc3R5bGU+PGc+PHBhdGggY2xhc3M9InN0MCIgZD0iTTc5LjI5LDEzLjYxYzAsMC4xMS0wLjA5LDAuMi0wLjIsMC4yaC0xLjUzYy0wLjEyLDAtMC4yMy0wLjA2LTAuMjktMC4xNmwtMS4zNy0yLjI4bC0xLjM3LDIuMjhjLTAuMDYsMC4xLTAuMTcsMC4xNi0wLjI5LDAuMTZoLTEuNTNjLTAuMDQsMC0wLjA4LTAuMDEtMC4xMS0wLjAzYy0wLjA5LTAuMDYtMC4xMi0wLjE4LTAuMDYtMC4yN2MwLDAsMCwwLDAsMGwyLjMxLTMuNWwtMi4yOC0zLjQ3Yy0wLjAyLTAuMDMtMC4wMy0wLjA3LTAuMDMtMC4xMWMwLTAuMTEsMC4wOS0wLjIsMC4yLTAuMmgxLjUzYzAuMTIsMCwwLjIzLDAuMDYsMC4yOSwwLjE2bDEuMzQsMi4yNWwxLjMzLTIuMjRjMC4wNi0wLjEsMC4xNy0wLjE2LDAuMjktMC4xNmgxLjUzYzAuMDQsMCwwLjA4LDAuMDEsMC4xMSwwLjAzYzAuMDksMC4wNiwwLjEyLDAuMTgsMC4wNiwwLjI3YzAsMCwwLDAsMCwwTDc2Ljk2LDEwbDIuMzEsMy41Qzc5LjI4LDEzLjUzLDc5LjI5LDEzLjU3LDc5LjI5LDEzLjYxeiIvPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik02My4wOSw5LjE2Yy0wLjM3LTEuNzktMS44Ny0zLjEyLTMuNjYtMy4xMmMtMC45OCwwLTEuOTMsMC40LTIuNiwxLjEyVjMuMzdjMC0wLjEyLTAuMS0wLjIyLTAuMjItMC4yMmgtMS4zM2MtMC4xMiwwLTAuMjIsMC4xLTAuMjIsMC4yMnYxMC4yMWMwLDAuMTIsMC4xLDAuMjIsMC4yMiwwLjIyaDEuMzNjMC4xMiwwLDAuMjItMC4xLDAuMjItMC4yMnYtMC43YzAuNjgsMC43MSwxLjYyLDEuMTIsMi42LDEuMTJjMS43OSwwLDMuMjktMS4zNCwzLjY2LTMuMTNDNjMuMjEsMTAuMyw2My4yMSw5LjcyLDYzLjA5LDkuMTZMNjMuMDksOS4xNnogTTU5LjEyLDEyLjQxYy0xLjI2LDAtMi4yOC0xLjA2LTIuMy0yLjM2VjkuOTljMC4wMi0xLjMxLDEuMDQtMi4zNiwyLjMtMi4zNnMyLjMsMS4wNywyLjMsMi4zOVM2MC4zOSwxMi40MSw1OS4xMiwxMi40MXoiLz48cGF0aCBjbGFzcz0ic3QwIiBkPSJNNjguMjYsNi4wNGMtMS44OS0wLjAxLTMuNTQsMS4yOS0zLjk2LDMuMTNjLTAuMTIsMC41Ni0wLjEyLDEuMTMsMCwxLjY5YzAuNDIsMS44NSwyLjA3LDMuMTYsMy45NywzLjE0YzIuMjQsMCw0LjA2LTEuNzgsNC4wNi0zLjk5UzcwLjUxLDYuMDQsNjguMjYsNi4wNHogTTY4LjI0LDEyLjQyYy0xLjI3LDAtMi4zLTEuMDctMi4zLTIuMzlzMS4wMy0yLjQsMi4zLTIuNHMyLjMsMS4wNywyLjMsMi4zOVM2OS41MSwxMi40MSw2OC4yNCwxMi40Mkw2OC4yNCwxMi40MnoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNNTkuMTIsNy42M2MtMS4yNiwwLTIuMjgsMS4wNi0yLjMsMi4zNnYwLjA2YzAuMDIsMS4zMSwxLjA0LDIuMzYsMi4zLDIuMzZzMi4zLTEuMDcsMi4zLTIuMzlTNjAuMzksNy42Myw1OS4xMiw3LjYzeiBNNTkuMTIsMTEuMjNjLTAuNiwwLTEuMDktMC41My0xLjExLTEuMTlWMTBjMC4wMS0wLjY2LDAuNTEtMS4xOSwxLjExLTEuMTlzMS4xMSwwLjU0LDEuMTEsMS4yMVM1OS43NCwxMS4yMyw1OS4xMiwxMS4yM3oiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNNjguMjQsNy42M2MtMS4yNywwLTIuMywxLjA3LTIuMywyLjM5czEuMDMsMi4zOSwyLjMsMi4zOXMyLjMtMS4wNywyLjMtMi4zOVM2OS41MSw3LjYzLDY4LjI0LDcuNjN6IE02OC4yNCwxMS4yM2MtMC42MSwwLTEuMTEtMC41NC0xLjExLTEuMjFzMC41LTEuMiwxLjExLTEuMnMxLjExLDAuNTQsMS4xMSwxLjIxUzY4Ljg1LDExLjIzLDY4LjI0LDExLjIzeiIvPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00My41Niw2LjI0aC0xLjMzYy0wLjEyLDAtMC4yMiwwLjEtMC4yMiwwLjIydjAuN2MtMC42OC0wLjcxLTEuNjItMS4xMi0yLjYtMS4xMmMtMi4wNywwLTMuNzUsMS43OC0zLjc1LDMuOTlzMS42OSwzLjk5LDMuNzUsMy45OWMwLjk5LDAsMS45My0wLjQxLDIuNi0xLjEzdjAuN2MwLDAuMTIsMC4xLDAuMjIsMC4yMiwwLjIyaDEuMzNjMC4xMiwwLDAuMjItMC4xLDAuMjItMC4yMlY2LjQ0YzAtMC4xMS0wLjA5LTAuMjEtMC4yMS0wLjIxQzQzLjU3LDYuMjQsNDMuNTcsNi4yNCw0My41Niw2LjI0eiBNNDIuMDIsMTAuMDVjLTAuMDEsMS4zMS0xLjA0LDIuMzYtMi4zLDIuMzZzLTIuMy0xLjA3LTIuMy0yLjM5czEuMDMtMi40LDIuMjktMi40YzEuMjcsMCwyLjI4LDEuMDYsMi4zLDIuMzZMNDIuMDIsMTAuMDV6Ii8+PHBhdGggY2xhc3M9InN0MSIgZD0iTTM5LjcyLDcuNjNjLTEuMjcsMC0yLjMsMS4wNy0yLjMsMi4zOXMxLjAzLDIuMzksMi4zLDIuMzlzMi4yOC0xLjA2LDIuMy0yLjM2VjkuOTlDNDIsOC42OCw0MC45OCw3LjYzLDM5LjcyLDcuNjN6IE0zOC42MiwxMC4wMmMwLTAuNjcsMC41LTEuMjEsMS4xMS0xLjIxYzAuNjEsMCwxLjA5LDAuNTMsMS4xMSwxLjE5djAuMDRjLTAuMDEsMC42NS0wLjUsMS4xOC0xLjExLDEuMThTMzguNjIsMTAuNjgsMzguNjIsMTAuMDJ6Ii8+PHBhdGggY2xhc3M9InN0MCIgZD0iTTQ5LjkxLDYuMDRjLTAuOTgsMC0xLjkzLDAuNC0yLjYsMS4xMlY2LjQ1YzAtMC4xMi0wLjEtMC4yMi0wLjIyLTAuMjJoLTEuMzNjLTAuMTIsMC0wLjIyLDAuMS0wLjIyLDAuMjJ2MTAuMjFjMCwwLjEyLDAuMSwwLjIyLDAuMjIsMC4yMmgxLjMzYzAuMTIsMCwwLjIyLTAuMSwwLjIyLTAuMjJ2LTMuNzhjMC42OCwwLjcxLDEuNjIsMS4xMiwyLjYxLDEuMTJjMi4wNywwLDMuNzUtMS43OCwzLjc1LTMuOTlTNTEuOTgsNi4wNCw0OS45MSw2LjA0eiBNNDkuNiwxMi40MmMtMS4yNiwwLTIuMjgtMS4wNi0yLjMtMi4zNlY5Ljk5YzAuMDItMS4zMSwxLjA0LTIuMzcsMi4yOS0yLjM3YzEuMjYsMCwyLjMsMS4wNywyLjMsMi4zOVM1MC44NiwxMi40MSw0OS42LDEyLjQyTDQ5LjYsMTIuNDJ6Ii8+PHBhdGggY2xhc3M9InN0MSIgZD0iTTQ5LjYsNy42M2MtMS4yNiwwLTIuMjgsMS4wNi0yLjMsMi4zNnYwLjA2YzAuMDIsMS4zMSwxLjA0LDIuMzYsMi4zLDIuMzZzMi4zLTEuMDcsMi4zLTIuMzlTNTAuODYsNy42Myw0OS42LDcuNjN6IE00OS42LDExLjIzYy0wLjYsMC0xLjA5LTAuNTMtMS4xMS0xLjE5VjEwQzQ4LjUsOS4zNCw0OSw4LjgxLDQ5LjYsOC44MWMwLjYsMCwxLjExLDAuNTUsMS4xMSwxLjIxUzUwLjIxLDExLjIzLDQ5LjYsMTEuMjN6Ii8+PHBhdGggY2xhc3M9InN0MCIgZD0iTTM0LjM2LDEzLjU5YzAsMC4xMi0wLjEsMC4yMi0wLjIyLDAuMjJoLTEuMzRjLTAuMTIsMC0wLjIyLTAuMS0wLjIyLTAuMjJWOS4yNGMwLTAuOTMtMC43LTEuNjMtMS41NC0xLjYzYy0wLjc2LDAtMS4zOSwwLjY3LTEuNTEsMS41NGwwLjAxLDQuNDRjMCwwLjEyLTAuMSwwLjIyLTAuMjIsMC4yMmgtMS4zNGMtMC4xMiwwLTAuMjItMC4xLTAuMjItMC4yMlY5LjI0YzAtMC45My0wLjctMS42My0xLjU0LTEuNjNjLTAuODEsMC0xLjQ3LDAuNzUtMS41MiwxLjcxdjQuMjdjMCwwLjEyLTAuMSwwLjIyLTAuMjIsMC4yMmgtMS4zM2MtMC4xMiwwLTAuMjItMC4xLTAuMjItMC4yMlY2LjQ0YzAuMDEtMC4xMiwwLjEtMC4yMSwwLjIyLTAuMjFoMS4zM2MwLjEyLDAsMC4yMSwwLjEsMC4yMiwwLjIxdjAuNjNjMC40OC0wLjY1LDEuMjQtMS4wNCwyLjA2LTEuMDVoMC4wM2MxLjA0LDAsMS45OSwwLjU3LDIuNDgsMS40OGMwLjQzLTAuOSwxLjMzLTEuNDgsMi4zMi0xLjQ5YzEuNTQsMCwyLjc5LDEuMTksMi43NiwyLjY1TDM0LjM2LDEzLjU5eiIvPjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik04MC4zMiwxMi45N2wtMC4wNy0wLjEyTDc4LjM4LDEwbDEuODUtMi44MWMwLjQyLTAuNjQsMC4yNS0xLjQ5LTAuMzktMS45MmMtMC4wMS0wLjAxLTAuMDItMC4wMS0wLjAzLTAuMDJjLTAuMjItMC4xNC0wLjQ4LTAuMjEtMC43NC0wLjIxaC0xLjUzYy0wLjUzLDAtMS4wMywwLjI4LTEuMywwLjc0bC0wLjMyLDAuNTNsLTAuMzItMC41M2MtMC4yOC0wLjQ2LTAuNzctMC43NC0xLjMxLTAuNzRoLTEuNTNjLTAuNTcsMC0xLjA4LDAuMzUtMS4yOSwwLjg4Yy0yLjA5LTEuNTgtNS4wMy0xLjQtNi45MSwwLjQzYy0wLjMzLDAuMzItMC42MiwwLjY5LTAuODUsMS4wOWMtMC44NS0xLjU1LTIuNDUtMi42LTQuMjgtMi42Yy0wLjQ4LDAtMC45NiwwLjA3LTEuNDEsMC4yMlYzLjM3YzAtMC43OC0wLjYzLTEuNDEtMS40LTEuNDFoLTEuMzNjLTAuNzcsMC0xLjQsMC42My0xLjQsMS40djMuNTdjLTAuOS0xLjMtMi4zOC0yLjA4LTMuOTctMi4wOWMtMC43LDAtMS4zOSwwLjE1LTIuMDIsMC40NWMtMC4yMy0wLjE2LTAuNTEtMC4yNS0wLjgtMC4yNWgtMS4zM2MtMC40MywwLTAuODMsMC4yLTEuMSwwLjUzYy0wLjAyLTAuMDMtMC4wNC0wLjA1LTAuMDctMC4wOGMtMC4yNy0wLjI5LTAuNjUtMC40NS0xLjA0LTAuNDVoLTEuMzJjLTAuMjksMC0wLjU3LDAuMDktMC44LDAuMjVDNDAuOCw1LDQwLjEyLDQuODUsMzkuNDIsNC44NWMtMS43NCwwLTMuMjcsMC45NS00LjE2LDIuMzhjLTAuMTktMC40NC0wLjQ2LTAuODUtMC43OS0xLjE5Yy0wLjc2LTAuNzctMS44LTEuMTktMi44OC0xLjE5aC0wLjAxYy0wLjg1LDAuMDEtMS42NywwLjMxLTIuMzQsMC44NGMtMC43LTAuNTQtMS41Ni0wLjg0LTIuNDUtMC44NGgtMC4wM2MtMC4yOCwwLTAuNTUsMC4wMy0wLjgyLDAuMWMtMC4yNywwLjA2LTAuNTMsMC4xNS0wLjc4LDAuMjdjLTAuMi0wLjExLTAuNDMtMC4xNy0wLjY3LTAuMTdoLTEuMzNjLTAuNzgsMC0xLjQsMC42My0xLjQsMS40djcuMTRjMCwwLjc4LDAuNjMsMS40LDEuNCwxLjRoMS4zM2MwLjc4LDAsMS40MS0wLjYzLDEuNDEtMS40MWMwLDAsMCwwLDAsMFY5LjM1YzAuMDMtMC4zNCwwLjIyLTAuNTYsMC4zNC0wLjU2YzAuMTcsMCwwLjM2LDAuMTcsMC4zNiwwLjQ1djQuMzVjMCwwLjc4LDAuNjMsMS40LDEuNCwxLjRoMS4zNGMwLjc4LDAsMS40LTAuNjMsMS40LTEuNGwtMC4wMS00LjM1YzAuMDYtMC4zLDAuMjQtMC40NSwwLjMzLTAuNDVjMC4xNywwLDAuMzYsMC4xNywwLjM2LDAuNDV2NC4zNWMwLDAuNzgsMC42MywxLjQsMS40LDEuNGgxLjM0YzAuNzgsMCwxLjQtMC42MywxLjQtMS40di0wLjM2YzAuOTEsMS4yMywyLjM0LDEuOTYsMy44NywxLjk2YzAuNywwLDEuMzktMC4xNSwyLjAyLTAuNDVjMC4yMywwLjE2LDAuNTEsMC4yNSwwLjgsMC4yNWgxLjMyYzAuMjksMCwwLjU3LTAuMDksMC44LTAuMjV2MS45MWMwLDAuNzgsMC42MywxLjQsMS40LDEuNGgxLjMzYzAuNzgsMCwxLjQtMC42MywxLjQtMS40di0xLjY5YzAuNDYsMC4xNCwwLjk0LDAuMjIsMS40MiwwLjIxYzEuNjIsMCwzLjA3LTAuODMsMy45Ny0yLjF2MC41YzAsMC43OCwwLjYzLDEuNCwxLjQsMS40aDEuMzNjMC4yOSwwLDAuNTctMC4wOSwwLjgtMC4yNWMwLjYzLDAuMywxLjMyLDAuNDUsMi4wMiwwLjQ1YzEuODMsMCwzLjQzLTEuMDUsNC4yOC0yLjZjMS40NywyLjUyLDQuNzEsMy4zNiw3LjIyLDEuODljMC4xNy0wLjEsMC4zNC0wLjIxLDAuNS0wLjM0YzAuMjEsMC41MiwwLjcyLDAuODcsMS4yOSwwLjg2aDEuNTNjMC41MywwLDEuMDMtMC4yOCwxLjMtMC43NGwwLjM1LTAuNThsMC4zNSwwLjU4YzAuMjgsMC40NiwwLjc3LDAuNzQsMS4zMSwwLjc0aDEuNTJjMC43NywwLDEuMzktMC42MywxLjM4LTEuMzlDODAuNDcsMTMuMzgsODAuNDIsMTMuMTcsODAuMzIsMTIuOTdMODAuMzIsMTIuOTd6IE0zNC4xNSwxMy44MWgtMS4zNGMtMC4xMiwwLTAuMjItMC4xLTAuMjItMC4yMlY5LjI0YzAtMC45My0wLjctMS42My0xLjU0LTEuNjNjLTAuNzYsMC0xLjM5LDAuNjctMS41MSwxLjU0bDAuMDEsNC40NGMwLDAuMTItMC4xLDAuMjItMC4yMiwwLjIyaC0xLjM0Yy0wLjEyLDAtMC4yMi0wLjEtMC4yMi0wLjIyVjkuMjRjMC0wLjkzLTAuNy0xLjYzLTEuNTQtMS42M2MtMC44MSwwLTEuNDcsMC43NS0xLjUyLDEuNzF2NC4yN2MwLDAuMTItMC4xLDAuMjItMC4yMiwwLjIyaC0xLjMzYy0wLjEyLDAtMC4yMi0wLjEtMC4yMi0wLjIyVjYuNDRjMC4wMS0wLjEyLDAuMS0wLjIxLDAuMjItMC4yMWgxLjMzYzAuMTIsMCwwLjIxLDAuMSwwLjIyLDAuMjF2MC42M2MwLjQ4LTAuNjUsMS4yNC0xLjA0LDIuMDYtMS4wNWgwLjAzYzEuMDQsMCwxLjk5LDAuNTcsMi40OCwxLjQ4YzAuNDMtMC45LDEuMzMtMS40OCwyLjMyLTEuNDljMS41NCwwLDIuNzksMS4xOSwyLjc2LDIuNjVsMC4wMSw0LjkxQzM0LjM3LDEzLjcsMzQuMjcsMTMuOCwzNC4xNSwxMy44MUMzNC4xNSwxMy44MSwzNC4xNSwxMy44MSwzNC4xNSwxMy44MXogTTQzLjc4LDEzLjU5YzAsMC4xMi0wLjEsMC4yMi0wLjIyLDAuMjJoLTEuMzNjLTAuMTIsMC0wLjIyLTAuMS0wLjIyLTAuMjJ2LTAuNzFDNDEuMzQsMTMuNiw0MC40LDE0LDM5LjQyLDE0Yy0yLjA3LDAtMy43NS0xLjc4LTMuNzUtMy45OXMxLjY5LTMuOTksMy43NS0zLjk5YzAuOTgsMCwxLjkyLDAuNDEsMi42LDEuMTJ2LTAuN2MwLTAuMTIsMC4xLTAuMjIsMC4yMi0wLjIyaDEuMzNjMC4xMS0wLjAxLDAuMjEsMC4wOCwwLjIyLDAuMmMwLDAuMDEsMCwwLjAxLDAsMC4wMlYxMy41OXogTTQ5LjkxLDE0Yy0wLjk4LDAtMS45Mi0wLjQxLTIuNi0xLjEydjMuNzhjMCwwLjEyLTAuMSwwLjIyLTAuMjIsMC4yMmgtMS4zM2MtMC4xMiwwLTAuMjItMC4xLTAuMjItMC4yMlY2LjQ1YzAtMC4xMiwwLjEtMC4yMSwwLjIyLTAuMjFoMS4zM2MwLjEyLDAsMC4yMiwwLjEsMC4yMiwwLjIydjAuN2MwLjY4LTAuNzIsMS42Mi0xLjEyLDIuNi0xLjEyYzIuMDcsMCwzLjc1LDEuNzcsMy43NSwzLjk4UzUxLjk4LDE0LDQ5LjkxLDE0eiBNNjMuMDksMTAuODdDNjIuNzIsMTIuNjUsNjEuMjIsMTQsNTkuNDMsMTRjLTAuOTgsMC0xLjkyLTAuNDEtMi42LTEuMTJ2MC43YzAsMC4xMi0wLjEsMC4yMi0wLjIyLDAuMjJoLTEuMzNjLTAuMTIsMC0wLjIyLTAuMS0wLjIyLTAuMjJWMy4zN2MwLTAuMTIsMC4xLTAuMjIsMC4yMi0wLjIyaDEuMzNjMC4xMiwwLDAuMjIsMC4xLDAuMjIsMC4yMnYzLjc4YzAuNjgtMC43MSwxLjYyLTEuMTIsMi42LTEuMTFjMS43OSwwLDMuMjksMS4zMywzLjY2LDMuMTJDNjMuMjEsOS43Myw2My4yMSwxMC4zMSw2My4wOSwxMC44N0w2My4wOSwxMC44N0w2My4wOSwxMC44N3ogTTY4LjI2LDE0LjAxYy0xLjksMC4wMS0zLjU1LTEuMjktMy45Ny0zLjE0Yy0wLjEyLTAuNTYtMC4xMi0xLjEzLDAtMS42OWMwLjQyLTEuODUsMi4wNy0zLjE1LDMuOTctMy4xNGMyLjI1LDAsNC4wNiwxLjc4LDQuMDYsMy45OVM3MC41LDE0LjAxLDY4LjI2LDE0LjAxTDY4LjI2LDE0LjAxeiBNNzkuMDksMTMuODFoLTEuNTNjLTAuMTIsMC0wLjIzLTAuMDYtMC4yOS0wLjE2bC0xLjM3LTIuMjhsLTEuMzcsMi4yOGMtMC4wNiwwLjEtMC4xNywwLjE2LTAuMjksMC4xNmgtMS41M2MtMC4wNCwwLTAuMDgtMC4wMS0wLjExLTAuMDNjLTAuMDktMC4wNi0wLjEyLTAuMTgtMC4wNi0wLjI3YzAsMCwwLDAsMCwwbDIuMzEtMy41bC0yLjI4LTMuNDdjLTAuMDItMC4wMy0wLjAzLTAuMDctMC4wMy0wLjExYzAtMC4xMSwwLjA5LTAuMiwwLjItMC4yaDEuNTNjMC4xMiwwLDAuMjMsMC4wNiwwLjI5LDAuMTZsMS4zNCwyLjI1bDEuMzQtMi4yNWMwLjA2LTAuMSwwLjE3LTAuMTYsMC4yOS0wLjE2aDEuNTNjMC4wNCwwLDAuMDgsMC4wMSwwLjExLDAuMDNjMC4wOSwwLjA2LDAuMTIsMC4xOCwwLjA2LDAuMjdjMCwwLDAsMCwwLDBMNzYuOTYsMTBsMi4zMSwzLjVjMC4wMiwwLjAzLDAuMDMsMC4wNywwLjAzLDAuMTFDNzkuMjksMTMuNzIsNzkuMiwxMy44MSw3OS4wOSwxMy44MUM3OS4wOSwxMy44MSw3OS4wOSwxMy44MSw3OS4wOSwxMy44MUw3OS4wOSwxMy44MXoiLz48cGF0aCBjbGFzcz0ic3QwIiBkPSJNMTAsMS4yMWMtNC44NywwLTguODEsMy45NS04LjgxLDguODFzMy45NSw4LjgxLDguODEsOC44MXM4LjgxLTMuOTUsOC44MS04LjgxQzE4LjgxLDUuMTUsMTQuODcsMS4yMSwxMCwxLjIxeiBNMTQuMTgsMTIuMTljLTEuODQsMS44NC00LjU1LDIuMi02LjM4LDIuMmMtMC42NywwLTEuMzQtMC4wNS0yLTAuMTVjMCwwLTAuOTctNS4zNywyLjA0LTguMzljMC43OS0wLjc5LDEuODYtMS4yMiwyLjk4LTEuMjJjMS4yMSwwLDIuMzcsMC40OSwzLjIzLDEuMzVDMTUuOCw3LjczLDE1Ljg1LDEwLjUsMTQuMTgsMTIuMTl6Ii8+PHBhdGggY2xhc3M9InN0MSIgZD0iTTEwLDAuMDJjLTUuNTIsMC0xMCw0LjQ4LTEwLDEwczQuNDgsMTAsMTAsMTBzMTAtNC40OCwxMC0xMEMxOS45OSw0LjUsMTUuNTIsMC4wMiwxMCwwLjAyeiBNMTAsMTguODNjLTQuODcsMC04LjgxLTMuOTUtOC44MS04LjgxUzUuMTMsMS4yLDEwLDEuMnM4LjgxLDMuOTUsOC44MSw4LjgxQzE4LjgxLDE0Ljg5LDE0Ljg3LDE4LjgzLDEwLDE4LjgzeiIvPjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNC4wNCw1Ljk4Yy0xLjc1LTEuNzUtNC41My0xLjgxLTYuMi0wLjE0QzQuODMsOC44Niw1LjgsMTQuMjMsNS44LDE0LjIzczUuMzcsMC45Nyw4LjM5LTIuMDRDMTUuODUsMTAuNSwxNS44LDcuNzMsMTQuMDQsNS45OHogTTExLjg4LDkuODdsLTAuODcsMS43OGwtMC44Ni0xLjc4TDguMzgsOS4wMWwxLjc3LTAuODZsMC44Ni0xLjc4bDAuODcsMS43OGwxLjc3LDAuODZMMTEuODgsOS44N3oiLz48cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjEzLjY1LDkuMDEgMTEuODgsOS44NyAxMS4wMSwxMS42NSAxMC4xNSw5Ljg3IDguMzgsOS4wMSAxMC4xNSw4LjE1IDExLjAxLDYuMzcgMTEuODgsOC4xNSAiLz48L2c+PC9zdmc+); + background-repeat: no-repeat; + background-position: 0 0; + background-size: 65px 20px; +`; + +export const MapWatermark = (props: AnchorHTMLAttributes) => ( + + Mapbox + +); diff --git a/packages/tupaia-web/src/layout/MapLayout/index.ts b/packages/tupaia-web/src/layout/MapLayout/index.ts new file mode 100644 index 0000000000..7d473704f9 --- /dev/null +++ b/packages/tupaia-web/src/layout/MapLayout/index.ts @@ -0,0 +1,7 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +export { MapLayout } from './MapLayout'; +export { MapWatermark } from './MapWatermark'; diff --git a/packages/tupaia-web/src/layout/Sidebar/ExpandButton.tsx b/packages/tupaia-web/src/layout/Sidebar/ExpandButton.tsx new file mode 100644 index 0000000000..261d9bc6db --- /dev/null +++ b/packages/tupaia-web/src/layout/Sidebar/ExpandButton.tsx @@ -0,0 +1,43 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2021 Beyond Essential Systems Pty Ltd + */ + +import React from 'react'; +import styled from 'styled-components'; +import { KeyboardArrowLeft, KeyboardArrowRight } from '@material-ui/icons'; +import { TRANSPARENT_BLACK } from '../../constants'; + +const SemiCircle = styled.div` + position: absolute; + top: 50%; + left: -30px; + display: flex; + justify-content: center; + align-items: center; + background-color: ${TRANSPARENT_BLACK}; + min-height: 60px; + min-width: 30px; + border-top-left-radius: 60px; + border-bottom-left-radius: 60px; + cursor: pointer; + pointer-events: auto; +`; + +const CloseArrowIcon = styled(KeyboardArrowRight)` + margin-left: 5px; +`; + +const OpenArrowIcon = styled(KeyboardArrowLeft)` + margin-left: 5px; +`; + +interface ExpandButtonProps { + isExpanded: boolean; + setIsExpanded: () => void; +} + +export const ExpandButton = ({ isExpanded, setIsExpanded }: ExpandButtonProps) => { + const arrowIcon = isExpanded ? : ; + return {arrowIcon}; +}; diff --git a/packages/tupaia-web/src/layout/Sidebar/Sidebar.tsx b/packages/tupaia-web/src/layout/Sidebar/Sidebar.tsx new file mode 100644 index 0000000000..c5c540b671 --- /dev/null +++ b/packages/tupaia-web/src/layout/Sidebar/Sidebar.tsx @@ -0,0 +1,46 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { TRANSPARENT_BLACK } from '../../constants'; +import { ExpandButton } from './ExpandButton'; + +const MAX_SIDEBAR_EXPANDED_WIDTH = 1000; +const MAX_SIDEBAR_COLLAPSED_WIDTH = 350; + +const Panel = styled.div<{ + $isExpanded: boolean; +}>` + display: flex; + align-items: stretch; + flex-direction: column; + align-content: stretch; + position: relative; + overflow: visible; + background-color: ${TRANSPARENT_BLACK}; + pointer-events: auto; + height: 100%; + cursor: auto; + transition: width 0.5s ease, max-width 0.5s ease; + width: ${({ $isExpanded }) => ($isExpanded ? 45 : 30)}%; + min-width: 335px; + max-width: ${({ $isExpanded }) => + $isExpanded ? MAX_SIDEBAR_EXPANDED_WIDTH : MAX_SIDEBAR_COLLAPSED_WIDTH}px; +`; + +export const Sidebar = () => { + const [isExpanded, setIsExpanded] = useState(false); + + const toggleExpanded = () => { + setIsExpanded(!isExpanded); + }; + + return ( + + + + ); +}; diff --git a/packages/tupaia-web/src/layout/Sidebar/index.ts b/packages/tupaia-web/src/layout/Sidebar/index.ts new file mode 100644 index 0000000000..a1ac18fc21 --- /dev/null +++ b/packages/tupaia-web/src/layout/Sidebar/index.ts @@ -0,0 +1,6 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +export { Sidebar } from './Sidebar'; diff --git a/packages/tupaia-web/src/layout/TopBar/Logo.tsx b/packages/tupaia-web/src/layout/TopBar/Logo.tsx new file mode 100644 index 0000000000..02c14d97e4 --- /dev/null +++ b/packages/tupaia-web/src/layout/TopBar/Logo.tsx @@ -0,0 +1,89 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import React, { ReactNode } from 'react'; +import { Typography } from '@material-ui/core'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; +import { DEFAULT_URL, TUPAIA_LIGHT_LOGO_SRC } from '../../constants'; + +const LogoWrapper = styled.div` + flex-grow: 1; + height: 100%; + display: flex; + align-items: center; +`; + +const LogoImage = styled.img` + max-height: 80%; + width: auto; + max-width: 50px; + @media screen and (min-width: ${({ theme }) => theme.breakpoints.values.sm}px) { + max-width: 100%; + } +`; + +const LogoLink = styled(Link)` + cursor: pointer; + pointer-events: auto; + padding: 0.5em; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background: none; + border: none; +`; + +const Name = styled(Typography)` + font-style: normal; + font-weight: ${props => props.theme.typography.fontWeightBold}; + font-size: 1rem; + line-height: 1; + letter-spacing: 0.1rem; + @media screen and (min-width: ${({ theme }) => theme.breakpoints.values.sm}px) { + font-size: 1.2rem; + } + @media screen and (min-width: ${({ theme }) => theme.breakpoints.values.md}px) { + font-size: 1.5rem; + } +`; + +const NameWrapper = styled.div` + max-width: 100%; + ${LogoImage} + & { + margin-left: 1.2rem; + } +`; + +// If logo is from a custom landing page, don't wrap in clickable button +const LogoComponent = ({ + isCustomLandingPage, + children, +}: { + isCustomLandingPage: boolean; + children: ReactNode[]; +}) => (isCustomLandingPage ? <>{children} : {children}); + +export const Logo = () => { + // Here is where we should swap out the logo for the custom landing page logo if applicable + const logoSrc = TUPAIA_LIGHT_LOGO_SRC; + + // These will later come from custom landing pages, where applicable + const displayName = false; + const name = ''; + return ( + + + + {/** If a custom landing page has set to display the name in the header, display it here */} + {displayName && ( + + {name} + + )} + + + ); +}; diff --git a/packages/tupaia-web/src/layout/TopBar/TopBar.tsx b/packages/tupaia-web/src/layout/TopBar/TopBar.tsx new file mode 100644 index 0000000000..369314e83e --- /dev/null +++ b/packages/tupaia-web/src/layout/TopBar/TopBar.tsx @@ -0,0 +1,54 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React from 'react'; +import styled from 'styled-components'; +import { Logo } from './Logo'; +import { UserMenu } from '../UserMenu'; + +const TOP_BAR_HEIGHT = 60; +const TOP_BAR_HEIGHT_MOBILE = 50; +/* Both min height and height must be specified due to bugs in Firefox flexbox, that means that topbar height will be ignored even if using flex-basis. */ +const Header = styled.header<{ + $primaryColor?: string; + $secondaryColor?: string; +}>` + background-color: ${({ $primaryColor, theme }) => + $primaryColor || theme.palette.background.default}; + height: ${TOP_BAR_HEIGHT_MOBILE}px; + min-height: ${TOP_BAR_HEIGHT_MOBILE}px; + display: flex; + align-items: center; + z-index: 1000; + position: relative; + padding: 0 0.625em; + border-bottom: 1px solid rgba(151, 151, 151, 0.3); + > * { + background-color: ${({ $primaryColor, theme }) => + $primaryColor || theme.palette.background.default}; + } + button, + a, + p, + h1, + li { + color: ${({ $secondaryColor, theme }) => $secondaryColor || theme.palette.text.primary}; + } + @media screen and (min-width: ${({ theme }) => theme.breakpoints.values.sm}px) { + height: ${TOP_BAR_HEIGHT}px; + min-height: ${TOP_BAR_HEIGHT}px; + align-items: initial; + } +`; + +export const TopBar = () => { + // When handing custom landing pages, pass the primary and secondary colors to the Header component + return ( +
+ + +
+ ); +}; diff --git a/packages/tupaia-web/src/layout/TopBar/index.ts b/packages/tupaia-web/src/layout/TopBar/index.ts new file mode 100644 index 0000000000..a27c0a3605 --- /dev/null +++ b/packages/tupaia-web/src/layout/TopBar/index.ts @@ -0,0 +1,6 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +export { TopBar } from './TopBar'; diff --git a/packages/tupaia-web/src/layout/UserMenu/DrawerMenu.tsx b/packages/tupaia-web/src/layout/UserMenu/DrawerMenu.tsx new file mode 100644 index 0000000000..5cbc1cda2e --- /dev/null +++ b/packages/tupaia-web/src/layout/UserMenu/DrawerMenu.tsx @@ -0,0 +1,128 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { ReactNode } from 'react'; +import styled from 'styled-components'; +import { Drawer as MuiDrawer } from '@material-ui/core'; +import { IconButton } from '@tupaia/ui-components'; +import CloseIcon from '@material-ui/icons/Close'; +import { MenuItem, MenuList } from './MenuList'; + +/** + * DrawerMenu is a drawer menu used when the user is on a mobile device + */ + +const Drawer = styled(MuiDrawer)` + @media screen and (min-width: ${props => props.theme.breakpoints.values.md}px) { + display: none; + } +`; + +const MenuWrapper = styled.div` + padding: 0 1.5em; + li a, + li button { + font-size: 1.2em; + padding: 0.8em; + line-height: 1.4; + text-align: left; + width: 100%; + font-weight: ${props => props.theme.typography.fontWeightRegular}; + color: inherit; + } + a > span { + text-decoration: underline; + } +`; + +const Username = styled.p` + font-weight: ${props => props.theme.typography.fontWeightMedium}; + text-transform: uppercase; + margin: 0; + width: 100%; + padding: 0 1em; +`; + +const MenuHeaderWrapper = styled.div` + padding: 0; +`; + +const MenuHeaderContainer = styled.div<{ + $secondaryColor?: string; +}>` + border-bottom: 1px solid + ${({ $secondaryColor, theme }) => $secondaryColor || theme.palette.text.primary}; + display: flex; + justify-content: flex-end; + padding: 0.8em 0; + align-items: center; + color: ${({ $secondaryColor }) => $secondaryColor}; +`; + +const MenuCloseIcon = styled(CloseIcon)<{ + $secondaryColor?: string; +}>` + width: 1.2em; + height: 1.2em; + fill: ${({ $secondaryColor, theme }) => $secondaryColor || theme.palette.text.primary}; +`; + +const MenuCloseButton = styled(IconButton)` + padding: 0; +`; + +interface DrawerMenuProps { + children: ReactNode; + menuOpen: boolean; + onCloseMenu: () => void; + primaryColor?: string; + secondaryColor?: string; + isUserLoggedIn: boolean; + currentUserUsername?: string; +} + +export const DrawerMenu = ({ + children, + menuOpen, + onCloseMenu, + primaryColor, + secondaryColor, + isUserLoggedIn, + currentUserUsername, +}: DrawerMenuProps) => { + return ( + + + + + {currentUserUsername && {currentUserUsername}} + + + + + + + {/** If the user is not logged in, show the register and login buttons */} + {!isUserLoggedIn && ( + <> + + Log in + + + Register + + + )} + {children} + + + + ); +}; diff --git a/packages/tupaia-web/src/layout/UserMenu/MenuList.tsx b/packages/tupaia-web/src/layout/UserMenu/MenuList.tsx new file mode 100644 index 0000000000..354275464c --- /dev/null +++ b/packages/tupaia-web/src/layout/UserMenu/MenuList.tsx @@ -0,0 +1,102 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { ReactNode } from 'react'; +import styled from 'styled-components'; +import { Button, ListItem, ListItemProps } from '@material-ui/core'; +import { Link } from 'react-router-dom'; + +/** + * Menulist is a component that displays a list of menu items for the hamburger menu + */ +const MenuListWrapper = styled.ul<{ + $secondaryColor?: string; +}>` + list-style: none; + margin-block-start: 0; + margin-block-end: 0; + padding-inline-start: 0; + * { + color: ${({ $secondaryColor }) => $secondaryColor}; + } +`; + +const MenuItemButton = styled(Button)` + text-transform: none; + font-size: 1rem; + font-weight: ${props => props.theme.typography.fontWeightRegular}; + padding: 0.4em 1em; + line-height: 1.4; + width: 100%; + justify-content: flex-start; +`; + +const MenuItemLink = styled(Link)` + font-size: 1rem; + padding: 0.4em 1em; + line-height: 1.4; + width: 100%; + text-decoration: none; + &:hover, + &:focus { + text-decoration: none; + background-color: rgba(255, 255, 255, 0.08); + } +`; + +type MenuListItemType = ListItemProps & { + $secondaryColor?: string; +}; + +const MenuListItem = styled(ListItem)<{ + $secondaryColor?: string; +}>` + padding: 0; + color: ${({ $secondaryColor }) => $secondaryColor}; +` as MenuListItemType; + +interface MenuItemProps { + href?: string; + children: ReactNode; + onClick?: () => void; + onCloseMenu: () => void; + secondaryColor?: string; + target?: string; +} + +// If is a link, use a link component, else a button so that we have correct semantic HTML +export const MenuItem = ({ + href, + children, + onClick, + onCloseMenu, + secondaryColor, + target, +}: MenuItemProps) => { + const handleClickMenuItem = () => { + if (onClick) onClick(); + onCloseMenu(); + }; + return ( + + {href ? ( + + {children} + + ) : ( + {children} + )} + + ); +}; + +interface MenuListProps { + children: ReactNode; + secondaryColor?: string; +} + +export const MenuList = ({ children, secondaryColor }: MenuListProps) => { + return {children}; +}; diff --git a/packages/tupaia-web/src/layout/UserMenu/PopoverMenu.tsx b/packages/tupaia-web/src/layout/UserMenu/PopoverMenu.tsx new file mode 100644 index 0000000000..5c13ee9c58 --- /dev/null +++ b/packages/tupaia-web/src/layout/UserMenu/PopoverMenu.tsx @@ -0,0 +1,53 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { ReactNode } from 'react'; +import { Popover as MuiPopover } from '@material-ui/core'; +import { MenuList } from './MenuList'; +import styled from 'styled-components'; + +/** + * PopoverMenu is a popover menu used when the user is on a desktop device + */ + +const Popover = styled(MuiPopover)` + @media screen and (max-width: ${({ theme }) => theme.breakpoints.values.md}px) { + display: none; + } +`; + +interface PopoverMenuProps { + children: ReactNode; + primaryColor?: string; + menuOpen: boolean; + onCloseMenu: () => void; + secondaryColor?: string; +} +export const PopoverMenu = ({ + children, + primaryColor, + menuOpen, + onCloseMenu, + secondaryColor, +}: PopoverMenuProps) => { + return ( + document.getElementById('user-menu-button') as HTMLElement} + onClose={onCloseMenu} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + > + {children} + + ); +}; diff --git a/packages/tupaia-web/src/layout/UserMenu/UserMenu.tsx b/packages/tupaia-web/src/layout/UserMenu/UserMenu.tsx new file mode 100644 index 0000000000..2d1057cd70 --- /dev/null +++ b/packages/tupaia-web/src/layout/UserMenu/UserMenu.tsx @@ -0,0 +1,79 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import React, { ReactNode, useState } from 'react'; +import MuiMenuIcon from '@material-ui/icons/Menu'; +import { Button, MenuList, useTheme } from '@material-ui/core'; +import styled from 'styled-components'; +import { PopoverMenu } from './PopoverMenu'; +import { DrawerMenu } from './DrawerMenu'; + +const UserMenuContainer = styled.div<{ + secondaryColor?: string; +}>` + display: flex; + align-items: center; + color: ${({ secondaryColor, theme }) => secondaryColor || theme.palette.text.primary}; +`; + +const MenuButton = styled(Button)` + width: 2em; + min-width: 2em; + height: 2em; + text-align: right; + padding: 0; + pointer-events: auto; +`; + +const MenuIcon = styled(MuiMenuIcon)` + width: 100%; + height: 100%; +`; + +export const UserMenu = () => { + const [menuOpen, setMenuOpen] = useState(false); + const toggleUserMenu = () => { + setMenuOpen(!menuOpen); + }; + + const onCloseMenu = () => { + setMenuOpen(false); + }; + + // Here will be the menu items logic. These will probably come from a context somewhere that handles when a user is logged in, and when it is a custom landing page + const menuItems = [] as ReactNode[]; + + const theme = useTheme(); + + // these will later be updated to handle custom landing pages + const primaryColor = theme.palette.background.default; + const secondaryColor = theme.palette.text.primary; + + return ( + + + + + {/** PopoverMenu is for larger (desktop size) screens, and DrawerMenu is for mobile screens. Each component takes care of the hiding and showing at different screen sizes. Eventually all the props will come from a context */} + + {menuItems} + + + {menuItems} + + + ); +}; diff --git a/packages/tupaia-web/src/layout/UserMenu/index.ts b/packages/tupaia-web/src/layout/UserMenu/index.ts new file mode 100644 index 0000000000..bc3f79334a --- /dev/null +++ b/packages/tupaia-web/src/layout/UserMenu/index.ts @@ -0,0 +1,6 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +export { UserMenu } from './UserMenu'; diff --git a/packages/tupaia-web/src/layout/index.ts b/packages/tupaia-web/src/layout/index.ts index 652a972fc4..2b862370e6 100644 --- a/packages/tupaia-web/src/layout/index.ts +++ b/packages/tupaia-web/src/layout/index.ts @@ -2,3 +2,7 @@ * Tupaia * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ +export { AuthModal } from './AuthModal'; +export { Layout } from './Layout'; +export { MapLayout } from './MapLayout'; +export { Sidebar } from './Sidebar'; diff --git a/packages/tupaia-web/src/pages/LoginForm.tsx b/packages/tupaia-web/src/pages/LoginForm.tsx index 8fff11ced8..aaa4b7396e 100644 --- a/packages/tupaia-web/src/pages/LoginForm.tsx +++ b/packages/tupaia-web/src/pages/LoginForm.tsx @@ -3,7 +3,19 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React from 'react'; +import { AuthModal } from '../layout'; export const LoginForm = () => { - return
LoginForm
; + return ( + {}, + text: 'Login', + }} + > +

Login form goes here

+
+ ); }; diff --git a/packages/tupaia-web/src/pages/PasswordResetForm.tsx b/packages/tupaia-web/src/pages/PasswordResetForm.tsx index 23ac65c155..caf11b30a7 100644 --- a/packages/tupaia-web/src/pages/PasswordResetForm.tsx +++ b/packages/tupaia-web/src/pages/PasswordResetForm.tsx @@ -3,7 +3,8 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React from 'react'; +import { AuthModal } from '../layout'; export const PasswordResetForm = () => { - return
PasswordResetForm
; + return PasswordResetForm; }; diff --git a/packages/tupaia-web/src/pages/Project.tsx b/packages/tupaia-web/src/pages/Project.tsx index 58624d1348..a1c4acaad7 100644 --- a/packages/tupaia-web/src/pages/Project.tsx +++ b/packages/tupaia-web/src/pages/Project.tsx @@ -3,17 +3,28 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React from 'react'; -import { useParams } from 'react-router-dom'; +import styled from 'styled-components'; +import { MapLayout, Sidebar } from '../layout'; +const Container = styled.div` + display: flex; + flex-direction: row; + flex: 1; + overflow: hidden; +`; + +/** + * This is the layout for the project/* view. This contains the map and the sidebar, as well as any overlays that are not auth overlays (i.e. not needed in landing pages) + */ export const Project = () => { // Use these to fetch the project and any other entity info you might need - const { projectCode, entityCode, '*': dashboardCode } = useParams(); + // const { projectCode, entityCode, '*': dashboardCode } = useParams(); return ( -
-

Project: {projectCode}

-

Entity: {entityCode}

-

Dashboard: {dashboardCode}

-
+ + + + {/** This is where SessionExpiredDialog and any other overlays would go, as well as loading screen */} + ); }; diff --git a/packages/tupaia-web/src/pages/RegisterForm.tsx b/packages/tupaia-web/src/pages/RegisterForm.tsx index d13fd63870..3d9c43cee1 100644 --- a/packages/tupaia-web/src/pages/RegisterForm.tsx +++ b/packages/tupaia-web/src/pages/RegisterForm.tsx @@ -3,7 +3,8 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React from 'react'; +import { AuthModal } from '../layout'; export const RegisterForm = () => { - return
RegisterForm
; + return RegisterForm; }; diff --git a/packages/tupaia-web/src/pages/RequestAccessForm.tsx b/packages/tupaia-web/src/pages/RequestAccessForm.tsx index f40ceaa61a..a72144d0a6 100644 --- a/packages/tupaia-web/src/pages/RequestAccessForm.tsx +++ b/packages/tupaia-web/src/pages/RequestAccessForm.tsx @@ -3,7 +3,8 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React from 'react'; +import { AuthModal } from '../layout'; export const RequestAccessForm = () => { - return
RequestAccessForm
; + return RequestAccessForm; }; diff --git a/packages/tupaia-web/src/pages/VerifyEmailForm.tsx b/packages/tupaia-web/src/pages/VerifyEmailForm.tsx index 456dc92b09..422c76f4ad 100644 --- a/packages/tupaia-web/src/pages/VerifyEmailForm.tsx +++ b/packages/tupaia-web/src/pages/VerifyEmailForm.tsx @@ -3,7 +3,8 @@ * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ import React from 'react'; +import { AuthModal } from '../layout'; export const VerifyEmailForm = () => { - return
VerifyEmailForm
; + return VerifyEmailForm; }; diff --git a/packages/tupaia-web/src/theme/constants.ts b/packages/tupaia-web/src/theme/constants.ts new file mode 100644 index 0000000000..a15a725733 --- /dev/null +++ b/packages/tupaia-web/src/theme/constants.ts @@ -0,0 +1,12 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +/** + * Theme constants that don't require being in the entire theme object, ie. things that won't change if a custom theme was applied + */ + +export const FONT_SIZES = { + viewTitle: '2em', +}; diff --git a/packages/tupaia-web/src/theme/index.ts b/packages/tupaia-web/src/theme/index.ts index 652a972fc4..f180c0a582 100644 --- a/packages/tupaia-web/src/theme/index.ts +++ b/packages/tupaia-web/src/theme/index.ts @@ -2,3 +2,5 @@ * Tupaia * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ +export * from './constants'; +export * from './theme'; diff --git a/packages/tupaia-web/src/theme/theme.ts b/packages/tupaia-web/src/theme/theme.ts new file mode 100644 index 0000000000..83ba090035 --- /dev/null +++ b/packages/tupaia-web/src/theme/theme.ts @@ -0,0 +1,16 @@ +import { createMuiTheme } from '@material-ui/core'; + +export const theme = createMuiTheme({ + palette: { + type: 'dark', + primary: { + main: '#1978D4', // Main blue (as seen on primary buttons) + }, + secondary: { + main: '#ee6230', // Tupaia Orange + }, + background: { + default: '#262834', // Dark blue background + }, + }, +}); diff --git a/packages/tupaia-web/src/types/mui.d.ts b/packages/tupaia-web/src/types/mui.d.ts new file mode 100644 index 0000000000..57a570aaf7 --- /dev/null +++ b/packages/tupaia-web/src/types/mui.d.ts @@ -0,0 +1,7 @@ +import { ListItemProps as MuiListItemProps } from '@material-ui/core'; + +declare module '@material-ui/core' { + export type ListItemProps = Omit & { + button?: boolean; // override this to make it optional, due to issue with 'button' prop in MuiListItemProps throwing an error when it is undefined + }; +} diff --git a/packages/tupaia-web/stories/Introduction.mdx b/packages/tupaia-web/stories/Introduction.mdx deleted file mode 100644 index ff7fc71fbf..0000000000 --- a/packages/tupaia-web/stories/Introduction.mdx +++ /dev/null @@ -1,213 +0,0 @@ -import { Meta } from '@storybook/blocks'; -import Code from './assets/code-brackets.svg'; -import Colors from './assets/colors.svg'; -import Comments from './assets/comments.svg'; -import Direction from './assets/direction.svg'; -import Flow from './assets/flow.svg'; -import Plugin from './assets/plugin.svg'; -import Repo from './assets/repo.svg'; -import StackAlt from './assets/stackalt.svg'; - - - - - -# Welcome to Storybook - -Storybook helps you build UI components in isolation from your app's business logic, data, and context. -That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA. - -Browse example stories now by navigating to them in the sidebar. -View their code in the `stories` directory to learn how they work. -We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages. - -
Configure
- - - -
Learn
- - - -
- TipEdit the Markdown in{' '} - stories/Introduction.stories.mdx -
diff --git a/packages/tupaia-web/stories/components/modal.stories.tsx b/packages/tupaia-web/stories/components/modal.stories.tsx new file mode 100644 index 0000000000..85e4532f61 --- /dev/null +++ b/packages/tupaia-web/stories/components/modal.stories.tsx @@ -0,0 +1,28 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { Modal } from '../../src/components/Modal'; +import { useState } from 'react'; +import { Button } from '@material-ui/core'; + +const meta: Meta = { + title: 'components/Modal', + component: Modal, +}; + +export default meta; + +export const Simple = () => { + const [isOpen, setIsOpen] = useState(true); + return ( + <> + + setIsOpen(false)}> + Hi, I am a modal + + + ); +}; diff --git a/packages/tupaia-web/stories/layout/authModal.stories.tsx b/packages/tupaia-web/stories/layout/authModal.stories.tsx new file mode 100644 index 0000000000..389aeb50c4 --- /dev/null +++ b/packages/tupaia-web/stories/layout/authModal.stories.tsx @@ -0,0 +1,58 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { AuthModal } from '../../src/layout/AuthModal'; + +const meta: Meta = { + title: 'layout/AuthModal', + component: AuthModal, +}; + +export default meta; + +export const Simple = () => { + return ( + + Hi, I am an auth modal + + ); +}; + +export const PrimaryButton = () => { + return ( + {}, + }} + > + Hi, I am an auth modal + + ); +}; + +export const SecondaryButton = () => { + return ( + {}, + }} + secondaryButton={{ + text: 'Cancel', + onClick: () => {}, + }} + > + Hi, I am an auth modal + + ); +}; diff --git a/packages/tupaia-web/vite.config.ts b/packages/tupaia-web/vite.config.ts index eb7eae4fe5..b1faa2cd3c 100644 --- a/packages/tupaia-web/vite.config.ts +++ b/packages/tupaia-web/vite.config.ts @@ -14,6 +14,9 @@ const baseConfig = { port: 8088, open: true, }, + define: { + 'process.env': process.env, // to stop errors when libraries use 'process.env' + }, envPrefix: 'REACT_APP_', // to allow any existing REACT_APP_ env variables to be used; resolve: { preserveSymlinks: true, @@ -34,6 +37,7 @@ export default defineConfig(({ command }) => { return { ...baseConfig, define: { + ...baseConfig.define, global: {}, }, }; diff --git a/packages/ui-components/src/components/EnvBanner.tsx b/packages/ui-components/src/components/EnvBanner.tsx index bb688f0a7f..2e4b45ce94 100644 --- a/packages/ui-components/src/components/EnvBanner.tsx +++ b/packages/ui-components/src/components/EnvBanner.tsx @@ -24,13 +24,7 @@ const StyledBanner = styled(FlexCenter)<{ } `; -export const EnvBanner = ( - { - color = '#f39c12' - }: { - color?: CSSStyleDeclaration['color']; - } -) => { +export const EnvBanner = ({ color = '#f39c12' }: { color?: CSSStyleDeclaration['color'] }) => { const deploymentName = process.env.REACT_APP_DEPLOYMENT_NAME; if (