Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds theming to support multiple instances of the platform #1242

Closed
wants to merge 10 commits into from
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -53,7 +53,6 @@
"resolutions": {
"__NOTE__": "2021-05-28 storybook clash: https://github.com/storybookjs/storybook/issues/6505",
"webpack": "4.44.2",
"styled-components": "^4",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question Not sure what/why with this line item; removing unless someone says no…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that might have been related to storybook back when all package installs were hoisted to top-level, but as we now have nmHoistingLimits: workspaces I expect fine to remove

"__NOTE2__": "2021-07-06 ram issues: https://github.com/facebook/create-react-app/issues/10154",
"react-scripts/eslint-webpack-plugin": "2.4.1",
"__NOTE3__": "2021-10-03 terser issues: https://github.com/facebook/create-react-app/issues/6334",
Expand Down
41 changes: 41 additions & 0 deletions src/App.tsx
@@ -0,0 +1,41 @@
import React, { Component } from 'react'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lift primary application out of src/index into a standalone App so it can be wrapped in MobX observer method to ensure all children can be correctly re-rendered by MobX state changes.

import { observer, Provider } from 'mobx-react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyle } from './themes/app.globalStyles'
import { RootStore } from './stores'
import ErrorBoundary from './common/ErrorBoundary'
import { Routes } from './pages'

const rootStore = new RootStore()

/**
* Additional store and db exports for use in modern context consumers
* @example const {userStore} = useCommonStores()
*/
export const rootStoreContext = React.createContext<RootStore>(rootStore)
export const useCommonStores = () =>
React.useContext<RootStore>(rootStoreContext)
export const dbContext = React.createContext({ db: rootStore.dbV2 })
export const useDB = () => React.useContext(dbContext)

@observer
export class App extends Component {
render() {
return (
<>
<Provider {...rootStore.stores}>
<ThemeProvider
theme={rootStore.stores.themeStore.currentTheme.styles}
>
<>
<ErrorBoundary>
<Routes />
</ErrorBoundary>
<GlobalStyle />
</>
</ThemeProvider>
</Provider>
</>
)
}
}
1 change: 1 addition & 0 deletions src/assets/images/themes/fixing-fashion/logo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/images/themes/unbranded/logo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/Avatar/index.tsx
@@ -1,6 +1,6 @@
import { Component } from 'react'

import { Image, ImageProps } from 'rebass'
import { Image, ImageProps } from 'rebass/styled-components'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: We have a mixture of styled-components and vanilla Rebass components which use: https://emotion.sh/

Rebass does allow you to specifically use styled-component compatible variants, so we are switching everywhere to use those instead. https://rebassjs.org/guides/styled-components

We can easily reverse this decision and switch towards Emotion dropping Styled Components but this needs to be consistent!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember at one time the lead frontend (Benj) had wanted to move to emotion, but as we had only just moved from material -> styled components seemed like one step too far. Would definitely be outside scope of the PR to make a switch so seems like a sensible option until such a time if we did decide (npm trends still has styled-components way more popular but I have no strong opinion on the matter otherwise)

import { inject, observer } from 'mobx-react'
import { ProfileTypeLabel } from 'src/models/user_pp.models'
import Workspace from 'src/pages/User/workspace/Workspace'
Expand Down
22 changes: 11 additions & 11 deletions src/components/Button/index.tsx
@@ -1,13 +1,11 @@
import * as React from 'react';
import * as React from 'react'
import { Icon, IGlyphs } from 'src/components/Icons'
import {
Button as RebassButton,
ButtonProps as RebassButtonProps,
} from 'rebass'
import theme from 'src/themes/styled.theme'
} from 'rebass/styled-components'
import Text from 'src/components/Text'
import styled from 'styled-components'
// import { IBtnProps } from './index'

// extend to allow any default button props (e.g. onClick) to also be passed
export interface IBtnProps extends React.ButtonHTMLAttributes<HTMLElement> {
Expand All @@ -18,6 +16,7 @@ export interface IBtnProps extends React.ButtonHTMLAttributes<HTMLElement> {
medium?: boolean
large?: boolean
hasText?: boolean
theme?: any
}
export const small = (props: IBtnProps) =>
props.small
Expand Down Expand Up @@ -61,14 +60,15 @@ const BaseButton = styled(RebassButton)`
${large}
`

export const Button = (props: BtnProps) => (
<BaseButton {...props}>
{props.icon && <Icon glyph={props.icon} marginRight="4px" />}
<Text>{props.children}</Text>
</BaseButton>
)
export const Button = (props: BtnProps) => {
return (
<BaseButton {...props}>
{props.icon && <Icon glyph={props.icon} marginRight="4px" />}
<Text>{props.children}</Text>
</BaseButton>
)
}

Button.defaultProps = {
type: 'button',
theme,
}
2 changes: 1 addition & 1 deletion src/components/Comment/Comment.tsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { FaTrash, FaRegEdit } from 'react-icons/fa'
import { Flex } from 'rebass'
import { Flex } from 'rebass/styled-components'
import { useCommonStores } from 'src'
import { IComment } from 'src/models'
import { CommentHeader } from './CommentHeader'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Comment/CommentHeader.tsx
@@ -1,4 +1,4 @@
import { Box, Flex, Text } from 'rebass'
import { Box, Flex, Text } from 'rebass/styled-components'
import { FlagIconHowTos } from 'src/components/Icons/FlagIcon/FlagIcon'
import { IComment } from 'src/models'
import { Link } from 'src/components/Links'
Expand Down
3 changes: 1 addition & 2 deletions src/components/Comment/CommentTextArea.tsx
@@ -1,7 +1,6 @@
import React from 'react'
import styled from 'styled-components'
import { Box, Text } from 'rebass/styled-components'
import theme from 'src/themes/styled.theme'
import { Avatar } from '../Avatar'
import { useCommonStores } from 'src'
import { Link } from '../Links'
Expand All @@ -19,7 +18,7 @@ const TextAreaStyled = styled.textarea`
min-width: 100%;
max-width: 100%;
font-family: 'Inter', Arial, sans-serif;
font-size: ${theme.fontSizes[2] + 'px'};
font-size: ${props => props.theme.fontSizes[2] + 'px'};
border-radius: 5px;
resize: none;

Expand Down
115 changes: 67 additions & 48 deletions src/components/DevSiteHeader/DevSiteHeader.tsx
@@ -1,64 +1,83 @@
import { SITE, VERSION, DEV_SITE_ROLE } from 'src/config/config'
import Text from 'src/components/Text'
import theme from 'src/themes/styled.theme'
import { UserRole } from 'src/models'
import { Flex, Box } from 'rebass'
import { Flex, Box } from 'rebass/styled-components'
import Select from 'react-select'
import { observer } from 'mobx-react-lite'
import { useCommonStores } from 'src'

/**
* A simple header component that reminds developers that they are working on a dev
* version of the platform, and provide the option to toggle between different dev sites
*/
const DevSiteHeader = () => (
<>
{devSites.find(s => s.value === SITE) && (
<Flex
data-cy="devSiteHeader"
bg={theme.colors.red2}
width={1}
py={1}
px={1}
alignItems="center"
style={{ zIndex: 3001 }}
>
<Text color={'white'} medium txtcenter flex="1">
This is a dev version of the platform (v{VERSION})
</Text>
<Flex data-cy="devSiteRoleSelectContainer" alignItems="center" ml={2}>
<Text color={'white'} medium mr="1" title={SITE}>
View as:
const DevSiteHeader = observer(() => {
const { themeStore } = useCommonStores().stores
const { themeOptions } = themeStore
const theme = themeStore.currentTheme.styles
return (
<>
{devSites.find(s => s.value === SITE) && (
<Flex
data-cy="devSiteHeader"
bg={theme.colors.red2}
width={1}
py={1}
px={1}
alignItems="center"
style={{ zIndex: 3001 }}
>
<Text color={'white'} medium txtcenter flex="1">
This is a dev version of the platform (v{VERSION})
</Text>
<Box width="150px" mr={3}>
<Select
options={siteRoles}
placeholder="Role"
defaultValue={
siteRoles.find(s => s.value === DEV_SITE_ROLE) || siteRoles[0]
}
onChange={(s: any) => setSiteRole(s.value)}
/>
</Box>
<Flex data-cy="devSiteRoleSelectContainer" alignItems="center" ml={2}>
<Text color={'white'} medium mr="1" title={SITE}>
View as:
</Text>
<Box width="150px" mr={3}>
<Select
options={siteRoles}
placeholder="Role"
defaultValue={
siteRoles.find(s => s.value === DEV_SITE_ROLE) || siteRoles[0]
}
onChange={(s: any) => setSiteRole(s.value)}
/>
</Box>
</Flex>
<Flex data-cy="devSiteSelectContainer" alignItems="center">
<Text color={'white'} medium mr="1" title={SITE}>
Site:
</Text>
<Box width="130px">
<Select
options={devSites}
placeholder="Site"
defaultValue={devSites.find(s => s.value === SITE)}
onChange={(s: any) => setSite(s.value)}
/>
</Box>
</Flex>
<Flex data-cy="themeSelectContainer" alignItems="center" ml={2}>
<Text color={'white'} medium ml="1" mr="1" title={SITE}>
Theme:
</Text>
<Box width="190px">
<Select
options={themeOptions}
placeholder="Site"
defaultValue={themeOptions[0]}
onChange={(s: any) => themeStore.setActiveTheme(s.value)}
/>
</Box>
</Flex>
</Flex>
<Flex data-cy="devSiteSelectContainer" alignItems="center">
<Text color={'white'} medium mr="1" title={SITE}>
Site:
</Text>
<Box width="130px">
<Select
options={devSites}
placeholder="Site"
defaultValue={devSites.find(s => s.value === SITE)}
onChange={(s: any) => setSite(s.value)}
/>
</Box>
</Flex>
</Flex>
)}
</>
)
)}
</>
)
})
// we have 2 different dev sites, only show this component when on one and provide select
const devSites = [
{ value: 'localhost', label: 'Dev' },
{ value: 'dev_site', label: 'Development' },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this, was wondering why I hadn't seen it recently :S

{ value: 'preview', label: 'Preview' },
]
// dev site users can use either a default user profile or mock another admin role
Expand Down
2 changes: 1 addition & 1 deletion src/components/DropdownIndicator/index.tsx
@@ -1,5 +1,5 @@
import { components } from 'react-select'
import { Image } from 'rebass'
import { Image } from 'rebass/styled-components'
import ArrowSelectIcon from '../../assets/icons/icon-arrow-select.svg'

// https://github.com/JedWatson/react-select/issues/685#issuecomment-420213835
Expand Down
2 changes: 1 addition & 1 deletion src/components/ElWithBeforeIcon/index.tsx
@@ -1,5 +1,5 @@
import { FunctionComponent } from 'react';
import { Box } from 'rebass'
import { Box } from 'rebass/styled-components'
import checkmarkIcon from 'src/assets/icons/icon-checkmark.svg'

interface IProps {
Expand Down
5 changes: 2 additions & 3 deletions src/components/FileInfo/FileDetails.tsx
@@ -1,9 +1,8 @@
import Icon, { availableGlyphs } from '../Icons'
import { Flex } from 'rebass'
import { Flex } from 'rebass/styled-components'
import Text from '../Text'
import { IUploadedFileMeta } from 'src/stores/storage'
import styled from 'styled-components'
import theme from 'src/themes/styled.theme'

interface IProps {
file: File | IUploadedFileMeta
Expand All @@ -14,7 +13,7 @@ interface IProps {
const FileFlex = styled(Flex)`
border: 2px solid black;
border-radius: 5px;
background-color: ${theme.colors.yellow.base};
background-color: ${props => props.theme.colors.yellow.base};
color: black;
`

Expand Down
2 changes: 1 addition & 1 deletion src/components/FileInput/FileInput.tsx
Expand Up @@ -5,7 +5,7 @@ import Uppy from '@uppy/core'
import { DashboardModal } from '@uppy/react'
import { Button } from '../Button'
import { UPPY_CONFIG } from './UppyConfig'
import { Flex } from 'rebass'
import { Flex } from 'rebass/styled-components'
import { FileInfo } from '../FileInfo/FileInfo'

interface IUppyFiles {
Expand Down
Expand Up @@ -4,9 +4,9 @@ import FileUploader from 'react-firebase-file-uploader'
import { FullMetadata } from '@firebase/storage-types'
import { Button } from '../Button'
import { IGlyphs } from '../Icons'
import { Flex } from 'rebass'
import Loader from '../Loader'
import { logger } from 'src/logger'
import { Flex } from 'rebass/styled-components'
/*
This component takes a folder storage path and uploads files to firebase storage
onUploadSucess allows URLs of completed uploads to be passed back to parent component
Expand Down
2 changes: 1 addition & 1 deletion src/components/Form/FlagSelect.tsx
@@ -1,6 +1,6 @@
import { FieldContainer, ErrorMessage } from './elements'
import ReactFlagsSelect from 'react-flags-select'
import { Flex } from 'rebass'
import { Flex } from 'rebass/styled-components'
import { IFieldProps } from './Fields'

export const FlagSelectField = ({ input, meta }: IFieldProps) => (
Expand Down
18 changes: 9 additions & 9 deletions src/components/Form/elements.ts
@@ -1,26 +1,26 @@
import styled, { css } from 'styled-components'
import theme from 'src/themes/styled.theme'
import DatePicker from 'react-datepicker'
interface IFormElement {
invalid?: boolean
customChange?: (location) => void
}
export const inputStyles = ({ invalid }: IFormElement) => css`
border: 1px solid ${invalid ? theme.colors.error : 'transparent'};
border: 1px solid
${invalid ? props => props.theme.colors.error : 'transparent'};
border-radius: 5px;
font-family: 'Inter', Arial, sans-serif;
font-size: ${theme.fontSizes[1] + 'px'};
background: ${theme.colors.background};
font-size: ${props => props.theme.fontSizes[1] + 'px'};
background: ${props => props.theme.colors.background};
width: 100%;
box-sizing: border-box;

&:disabled {
border: none;
color: ${theme.colors.black};
color: ${props => props.theme.colors.black};
}

&:focus {
border-color: ${theme.colors.blue};
border-color: ${props => props.theme.colors.blue};
outline: none;
}
`
Expand Down Expand Up @@ -67,8 +67,8 @@ export const FieldContainer = styled.div<IFormElement>`
`
export const ErrorMessage = styled.span`
position: relative;
color: ${theme.colors.error};
font-size: ${theme.fontSizes[0]}px;
height: ${theme.space[0]};
color: ${props => props.theme.colors.error};
font-size: ${props => props.theme.fontSizes[0]}px;
height: ${props => props.theme.space[0]};
margin: 5px;
`