diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5109b4e34d..3b918a1976 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,7 +32,7 @@ repos: rev: 5.10.1 hooks: - id: isort - args: ["--profile", "black", "--filter-files"] + args: ['--profile', 'black', '--filter-files'] exclude: ^.*\b(migrations)\b.*$ # Pythonic checks. - repo: https://github.com/PyCQA/flake8 @@ -41,7 +41,7 @@ repos: - id: flake8 exclude: docs|migrations|node_modules|revengine/settings additional_dependencies: - - "flake8-logging-format" + - 'flake8-logging-format' # # Pylint code checks. # # "local" because pylint needs all packages to dynamically import. # - repo: local @@ -77,7 +77,7 @@ repos: rev: v2.7.1 hooks: - id: prettier - types: [javascript] + types_or: [javascript, ts, tsx] # JS code linter. - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.18.0 diff --git a/spa/src/assets/assets.d.ts b/spa/src/assets/assets.d.ts new file mode 100644 index 0000000000..077b8ac65d --- /dev/null +++ b/spa/src/assets/assets.d.ts @@ -0,0 +1,11 @@ +// Required to import .svg files to TS files +declare module '*.svg' { + const content: any; + export default content; +} + +// Required to import .png files to TS files +declare module '*.png' { + const content: any; + export default content; +} diff --git a/spa/src/assets/icons/logout.svg b/spa/src/assets/icons/logout.svg new file mode 100644 index 0000000000..7c2f2b5595 --- /dev/null +++ b/spa/src/assets/icons/logout.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.stories.js b/spa/src/components/common/AvatarMenu/AvatarMenu.stories.js new file mode 100644 index 0000000000..1706f33659 --- /dev/null +++ b/spa/src/components/common/AvatarMenu/AvatarMenu.stories.js @@ -0,0 +1,40 @@ +import AvatarMenu from './AvatarMenu'; + +export default { + title: 'Common/AvatarMenu', + component: AvatarMenu +}; + +export const Default = (props) => ( +
+ +
+); + +Default.args = { + user: { + firstName: 'Gui', + lastName: 'Mend', + email: 'gm@gmail.com' + } +}; + +export const NoNameAvatar = (props) => ( +
+ +
+); + +NoNameAvatar.args = { + user: { + email: 'gm@gmail.com' + } +}; + +export const EmptyAvatar = (props) => ( +
+ +
+); + +EmptyAvatar.args = {}; diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.styled.ts b/spa/src/components/common/AvatarMenu/AvatarMenu.styled.ts new file mode 100644 index 0000000000..3732276e17 --- /dev/null +++ b/spa/src/components/common/AvatarMenu/AvatarMenu.styled.ts @@ -0,0 +1,110 @@ +import { + Avatar as MuiAvatar, + Popover as MuiPopover, + MenuItem as MuiMenuItem, + Typography as MuiTypography, + ListItemIcon as MuiListItemIcon +} from '@material-ui/core'; +import styled from 'styled-components'; + +export const Container = styled.button<{ open: string }>` + display: flex; + align-items: center; + cursor: pointer; + padding: 0; + background: transparent; + border: none; + padding: 9px 0 9px 10px; + max-height: 48px; + + svg { + fill: white; + height: 27px; + width: 27px; + margin-left: -3px; + } + + ${(props) => + props.open === 'open' && + `background-color: ${props.theme.colors.account.purple[1]}; + `} + + :hover { + background-color: ${(props) => props.theme.colors.account.purple[1]}; + } +`; + +export const ModalHeader = styled.p` + font-size: ${(props) => props.theme.fontSizesUpdated.sm}; + font-family: ${(props) => props.theme.systemFont}; + color: ${(props) => props.theme.colors.muiGrey[700]}; + font-weight: 600; + margin: 0 0 9px; +`; + +export const ListWrapper = styled.ul` + padding: 0; + margin: 0; +`; + +export const ListItemIcon = styled(MuiListItemIcon)` + && { + min-width: unset; + color: ${(props) => props.theme.colors.sidebarBackground}; + + svg { + width: 20px; + height: 20px; + } + } +`; + +export const LogoutIconWrapper = styled.img` + width: 20px; + height: 20px; +`; + +export const Avatar = styled(MuiAvatar)` + && { + height: 31px; + width: 31px; + border: 1px solid white; + font-size: ${(props) => props.theme.fontSizesUpdated.sm}; + background-color: ${(props) => props.theme.colors.muiLightBlue[800]}; + } +`; + +export const Popover = styled(MuiPopover)` + .MuiPaper-rounded { + min-width: 207px; + box-shadow: none; + padding: 12px 15px; + border: 1px solid ${(props) => props.theme.colors.muiGrey[300]}; + background-color: ${(props) => props.theme.colors.muiGrey[100]}; + filter: drop-shadow(0px 2px 20px rgba(0, 0, 0, 0.1)); + } +`; + +export const Typography = styled(MuiTypography)` + && { + color: ${(props) => props.theme.colors.sidebarBackground}; + font-size: ${(props) => props.theme.fontSizesUpdated.sm}; + font-weight: 400; + } +`; + +export const MenuItem = styled(MuiMenuItem)` + && { + padding: 5px 7px; + margin: 0 -7px; + border-radius: ${(props) => props.theme.muiBorderRadius.lg}; + background-color: ${(props) => props.theme.colors.muiGrey[100]}; + display: flex; + align-items: center; + gap: 18px; + + :hover { + background-color: ${(props) => props.theme.colors.navSectionLabelColor}; + } + } +`; diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.test.tsx b/spa/src/components/common/AvatarMenu/AvatarMenu.test.tsx new file mode 100644 index 0000000000..3b9d67ccfe --- /dev/null +++ b/spa/src/components/common/AvatarMenu/AvatarMenu.test.tsx @@ -0,0 +1,102 @@ +import { axe } from 'jest-axe'; +import { render, screen, within } from 'test-utils'; +import userEvent from '@testing-library/user-event'; + +import { FAQ_URL } from 'constants/helperUrls'; +import onLogout from 'components/authentication/logout'; +import AvatarMenu, { AvatarMenuProps } from './AvatarMenu'; + +jest.mock('components/authentication/logout'); + +const tree = (props?: AvatarMenuProps) => { + return render(); +}; + +const settingsMenu = 'Settings'; + +describe('AvatarMenu', () => { + it('should be enabled if has user', () => { + tree({ user: { email: 'a@a.com' } }); + expect(screen.getByRole('button', { name: settingsMenu })).toBeEnabled(); + }); + + it('should be disabled if user is undefined', () => { + tree(); + expect(screen.getByRole('button', { name: settingsMenu })).toBeEnabled(); + }); + + it('should render name initials', () => { + const nameUser = { + firstName: 'first-mock', + lastName: 'last-mock', + email: 'email@mock.com' + }; + tree({ user: nameUser }); + + const avatar = screen.getByTestId('avatar'); + const title = within(avatar).getByText('FL'); + expect(title).toBeInTheDocument(); + }); + + it('should render email initial if name is empty', () => { + const emailUser = { + email: 'email@mock.com' + }; + tree({ user: emailUser }); + + const avatar = screen.getByTestId('avatar'); + expect(avatar.innerHTML).toBe('E'); + const title = within(avatar).getByText('E'); + expect(title).toBeInTheDocument(); + }); + + it('should render nothing if user is undefined', () => { + tree(); + + const avatar = screen.getByTestId('avatar'); + expect(avatar.innerHTML).toBe(''); + }); + + it('should open settings menu with correct buttons', () => { + const emailUser = { + email: 'email@mock.com' + }; + tree({ user: emailUser }); + + userEvent.click(screen.getByRole('button', { name: settingsMenu })); + expect(screen.getByRole('menuitem', { name: 'FAQ' })).toBeEnabled(); + expect(screen.getByRole('menuitem', { name: 'Sign out' })).toBeEnabled(); + }); + + it('should have correct FAQ link', () => { + const oldOpen = jest.spyOn(window, 'open').mockImplementation(); + const emailUser = { + email: 'email@mock.com' + }; + tree({ user: emailUser }); + + userEvent.click(screen.getByRole('button', { name: settingsMenu })); + userEvent.click(screen.getByRole('menuitem', { name: 'FAQ' })); + expect(oldOpen).toBeCalledWith(FAQ_URL, '_blank', 'noopener, noreferrer'); + oldOpen.mockRestore(); + }); + + it('should logout', () => { + const emailUser = { + email: 'email@mock.com' + }; + tree({ user: emailUser }); + + userEvent.click(screen.getByRole('button', { name: settingsMenu })); + userEvent.click(screen.getByRole('menuitem', { name: 'Sign out' })); + expect(onLogout).toBeCalledTimes(1); + }); + + it('is accessible', async () => { + const { container } = tree({ user: { email: 'a@a.com' } }); + expect(await axe(container)).toHaveNoViolations(); + + userEvent.click(screen.getByRole('button', { name: settingsMenu })); + expect(await axe(container)).toHaveNoViolations(); + }); +}); diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.tsx b/spa/src/components/common/AvatarMenu/AvatarMenu.tsx new file mode 100644 index 0000000000..834e0e0dcc --- /dev/null +++ b/spa/src/components/common/AvatarMenu/AvatarMenu.tsx @@ -0,0 +1,106 @@ +import { MouseEvent, useMemo, useState } from 'react'; +import PropTypes, { InferProps } from 'prop-types'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import ContactSupportOutlinedIcon from '@material-ui/icons/ContactSupportOutlined'; + +import LogoutIcon from 'assets/icons/logout.svg'; +import onLogout from 'components/authentication/logout'; +import { FAQ_URL } from 'constants/helperUrls'; + +import { + Container, + ModalHeader, + Popover, + Avatar, + ListWrapper, + MenuItem, + ListItemIcon, + Typography, + LogoutIconWrapper +} from './AvatarMenu.styled'; + +export type AvatarMenuProps = InferProps; + +export const capitalizeInitial = (text?: string) => (text ? text[0].toUpperCase() : ''); + +const AvatarMenu = ({ user, className }: AvatarMenuProps) => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const id = open ? 'avatar-menu-popover' : undefined; + + const avatarInitials = useMemo( + () => + user?.firstName + ? `${capitalizeInitial(user?.firstName || '')}${capitalizeInitial(user?.lastName || '')}` + : `${capitalizeInitial(user?.email || '')}`, + [user?.email, user?.firstName, user?.lastName] + ); + + const handleClick = (event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleFAQ = () => { + window.open(FAQ_URL, '_blank', 'noopener, noreferrer'); + }; + + return ( + <> + + {avatarInitials} + + + + Settings + + + + + + FAQ + + + + + + Sign out + + + + + ); +}; + +const AvatarMenuPropTypes = { + user: PropTypes.shape({ + firstName: PropTypes.string, + lastName: PropTypes.string, + email: PropTypes.string.isRequired + }), + className: PropTypes.string +}; + +AvatarMenu.propTypes = AvatarMenuPropTypes; + +AvatarMenu.defaultProps = { + className: '' +}; + +export default AvatarMenu; diff --git a/spa/src/components/common/AvatarMenu/index.ts b/spa/src/components/common/AvatarMenu/index.ts new file mode 100644 index 0000000000..bccc85e195 --- /dev/null +++ b/spa/src/components/common/AvatarMenu/index.ts @@ -0,0 +1 @@ +export { default } from './AvatarMenu'; diff --git a/spa/src/components/dashboard/Dashboard.js b/spa/src/components/dashboard/Dashboard.js index 6b30089fea..1df812e70d 100644 --- a/spa/src/components/dashboard/Dashboard.js +++ b/spa/src/components/dashboard/Dashboard.js @@ -57,7 +57,7 @@ function Dashboard() { return ( {requiresVerification ? : ''} - + {isEditPage ? null : } diff --git a/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js b/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js index 0f7b62cf0c..6886e10a8f 100644 --- a/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js +++ b/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js @@ -1,19 +1,14 @@ import * as S from './DashboardSidebar.styled'; import { ICONS } from 'assets/icons/SvgIcon'; import HelpOutlined from '@material-ui/icons/HelpOutline'; - -// Exported mainly to help with unit tests. -export const footerHrefs = { - faq: 'https://news-revenue-hub.atlassian.net/servicedesk/customer/portal/11/article/2195423496', - help: 'https://fundjournalism.org/news-revenue-engine-help/' -}; +import { FAQ_URL, HELP_URL } from 'constants/helperUrls'; const DashboardSidebarFooter = () => ( ( { function tree(props) { @@ -17,7 +18,7 @@ describe('DashboardSidebarFooter', () => { const faqLink = screen.getByRole('listitem', { name: 'FAQ' }); expect(faqLink).toBeVisible(); - expect(faqLink).toHaveAttribute('href', footerHrefs.faq); + expect(faqLink).toHaveAttribute('href', FAQ_URL); expect(faqLink).toHaveAttribute('target', '_blank'); }); @@ -27,7 +28,7 @@ describe('DashboardSidebarFooter', () => { const helpLink = screen.getByRole('listitem', { name: 'Help' }); expect(helpLink).toBeVisible(); - expect(helpLink).toHaveAttribute('href', footerHrefs.help); + expect(helpLink).toHaveAttribute('href', HELP_URL); expect(helpLink).toHaveAttribute('target', '_blank'); }); diff --git a/spa/src/components/dashboard/topbar/DashboardTopbar.test.js b/spa/src/components/dashboard/topbar/DashboardTopbar.test.js index e70c44686e..f349bd4545 100644 --- a/spa/src/components/dashboard/topbar/DashboardTopbar.test.js +++ b/spa/src/components/dashboard/topbar/DashboardTopbar.test.js @@ -31,11 +31,14 @@ const page = { published_date: '2021-11-18T21:51:53Z' }; +const user = { + email: 'mock@email.com' +}; + describe('Dashboard TopBar', () => { - it('should show logout link in topbar', () => { - render(); - fireEvent.click(screen.getByText('Sign out')); - expect(logout).toHaveBeenCalled(); + it('should show avatar menu in topbar', () => { + render(); + expect(screen.getByRole('button', { name: 'Settings' })).toBeEnabled(); }); it('should hide grab link button if isEditPage = false', () => { diff --git a/spa/src/components/dashboard/topbar/DashboardTopbar.js b/spa/src/components/dashboard/topbar/DashboardTopbar.tsx similarity index 83% rename from spa/src/components/dashboard/topbar/DashboardTopbar.js rename to spa/src/components/dashboard/topbar/DashboardTopbar.tsx index fe673fab47..2cadaefdf7 100644 --- a/spa/src/components/dashboard/topbar/DashboardTopbar.js +++ b/spa/src/components/dashboard/topbar/DashboardTopbar.tsx @@ -1,25 +1,27 @@ -import PropTypes from 'prop-types'; -import * as S from './DashboardTopbar.styled'; -import { SvgLogo, Title, BackIconButton } from './DashboardTopbar.styled'; import { ICONS } from 'assets/icons/SvgIcon'; +import PropTypes, { InferProps } from 'prop-types'; import { useAlert } from 'react-alert'; +import * as S from './DashboardTopbar.styled'; +import { BackIconButton, SvgLogo, Title } from './DashboardTopbar.styled'; -import { PagePropTypes } from 'constants/proptypes'; -import { BackIcon } from 'elements/BackButton.styled'; -import { CONTENT_SLUG } from 'routes'; -import BackButton from 'elements/BackButton'; -import useRequest from 'hooks/useRequest'; +import mobileLogo from 'assets/images/logo-mobile.png'; import logo from 'assets/images/logo-nre.png'; import logoBlue from 'assets/images/nre-logo-blue.svg'; -import mobileLogo from 'assets/images/logo-mobile.png'; -import logout from 'components/authentication/logout'; +import { Tooltip } from 'components/base'; +import AvatarMenu from 'components/common/AvatarMenu'; import GrabLink from 'components/common/Button/GrabLink'; import PublishButton from 'components/common/Button/PublishButton'; import UnsavedChangesModal from 'components/pageEditor/UnsavedChangesModal'; +import { PagePropTypes, UserPropTypes } from 'constants/proptypes'; +import BackButton from 'elements/BackButton'; +import { BackIcon } from 'elements/BackButton.styled'; import useModal from 'hooks/useModal'; -import { Tooltip } from 'components/base'; +import useRequest from 'hooks/useRequest'; +import { CONTENT_SLUG } from 'routes'; -function DashboardTopbar({ isEditPage, page, setPage, updatedPage }) { +type DashboardTopbarTypes = InferProps; + +function DashboardTopbar({ isEditPage, page, setPage, updatedPage, user }: DashboardTopbarTypes) { const alert = useAlert(); const requestPatchPage = useRequest(); const { open: showUnsavedModal, handleClose: closeUnsavedModal, handleOpen: openUnsavedModal } = useModal(); @@ -61,28 +63,23 @@ function DashboardTopbar({ isEditPage, page, setPage, updatedPage }) { )} ) : ( - - - Sign out - + )} ); } -DashboardTopbar.propTypes = { +const DashboardTopbarPropTypes = { isEditPage: PropTypes.bool, setPage: PropTypes.func, page: PropTypes.shape(PagePropTypes), + user: PropTypes.shape(UserPropTypes), updatedPage: PropTypes.shape(PagePropTypes) }; +DashboardTopbar.propTypes = DashboardTopbarPropTypes; + DashboardTopbar.defaultProps = { isEditPage: false, page: undefined diff --git a/spa/src/constants/helperUrls.ts b/spa/src/constants/helperUrls.ts new file mode 100644 index 0000000000..ba198df4dd --- /dev/null +++ b/spa/src/constants/helperUrls.ts @@ -0,0 +1,2 @@ +export const FAQ_URL = 'https://news-revenue-hub.atlassian.net/servicedesk/customer/portal/11/article/2195423496'; +export const HELP_URL = 'https://fundjournalism.org/news-revenue-engine-help/'; diff --git a/spa/src/constants/proptypes.js b/spa/src/constants/proptypes.js index 01ed18d8af..50c8769b2d 100644 --- a/spa/src/constants/proptypes.js +++ b/spa/src/constants/proptypes.js @@ -11,3 +11,9 @@ export const PagePropTypes = { slug: PropTypes.string, published_date: PropTypes.string }; + +export const UserPropTypes = { + firstName: PropTypes.string, + lastName: PropTypes.string, + email: PropTypes.string.isRequired +}; diff --git a/spa/src/hooks/useFeatureFlags.ts b/spa/src/hooks/useFeatureFlags.ts index 9f3aeb6834..8684837218 100644 --- a/spa/src/hooks/useFeatureFlags.ts +++ b/spa/src/hooks/useFeatureFlags.ts @@ -6,7 +6,7 @@ interface UseFeatureFlagsHook { flags: FeatureFlag[]; isLoading: boolean; isError: boolean; -}; +} /* This hook provides any feature flags attached to the user. It depends on `useUser` which is responsible for retrieving the user from diff --git a/spa/src/hooks/useFeatureFlags.types.ts b/spa/src/hooks/useFeatureFlags.types.ts index fc24b01781..e1fcc381f3 100644 --- a/spa/src/hooks/useFeatureFlags.types.ts +++ b/spa/src/hooks/useFeatureFlags.types.ts @@ -1,3 +1,3 @@ export interface FeatureFlag { name: string; -}; +} diff --git a/spa/src/hooks/useStyleList.test.tsx b/spa/src/hooks/useStyleList.test.tsx index b57d31e622..52759decbe 100644 --- a/spa/src/hooks/useStyleList.test.tsx +++ b/spa/src/hooks/useStyleList.test.tsx @@ -6,17 +6,16 @@ import { ReactChild } from 'react'; import axios from 'ajax/axios'; import { LIST_STYLES } from 'ajax/endpoints'; -import useStyleList from "./useStyleList"; +import useStyleList from './useStyleList'; import { SIGN_IN } from 'routes'; import { GENERIC_ERROR } from 'constants/textConstants'; - const mockHistoryPush = jest.fn(); jest.mock('react-router-dom', () => ({ useHistory: () => ({ push: mockHistoryPush - }), + }) })); const mockAlertError = jest.fn(); @@ -25,25 +24,28 @@ jest.mock('react-alert', () => ({ useAlert: () => ({ error: mockAlertError }) })); - const axiosMock = new MockAdapter(axios); - describe('useStyleList hook', () => { - const wrapper = ({ children }: { children: ReactChild }) => ( - {children} + + {children} + ); const stylesList = [ { id: 1, styles: { foo: 'bar' } }, - { id: 2, styles: { bizz: 'bang' } }, + { id: 2, styles: { bizz: 'bang' } } ]; beforeEach(() => { @@ -60,8 +62,12 @@ describe('useStyleList hook', () => { const spy = jest.spyOn(reactQuery, 'useQueryClient'); // cast this to `as any` so we don't have to provide all 33 params that are in returned // queryClient in real implementation - spy.mockReturnValue({invalidateQueries: mockInvalidateQueries} as any) - const { result: { current: { refetch } } } = renderHook(() => useStyleList(), { wrapper }); + spy.mockReturnValue({ invalidateQueries: mockInvalidateQueries } as any); + const { + result: { + current: { refetch } + } + } = renderHook(() => useStyleList(), { wrapper }); expect(typeof refetch).toBe('function'); refetch(); expect(mockInvalidateQueries).toHaveBeenCalledWith(['styles']); @@ -83,12 +89,12 @@ describe('useStyleList hook', () => { }); it('redirects user to log in when auth error', async () => { axiosMock.reset(); - axiosMock.onGet(LIST_STYLES).reply((function (config) { - return Promise.reject({name: 'AuthenticationError'}); - })); + axiosMock.onGet(LIST_STYLES).reply(function (config) { + return Promise.reject({ name: 'AuthenticationError' }); + }); const { waitForValueToChange, result } = renderHook(() => useStyleList(), { wrapper }); await waitForValueToChange(() => result.current.isError); expect(mockHistoryPush).toHaveBeenCalledTimes(1); - expect(mockHistoryPush).toHaveBeenCalledWith(SIGN_IN) + expect(mockHistoryPush).toHaveBeenCalledWith(SIGN_IN); }); }); diff --git a/spa/src/hooks/useStyleList.ts b/spa/src/hooks/useStyleList.ts index d78331b2fe..44cf894045 100644 --- a/spa/src/hooks/useStyleList.ts +++ b/spa/src/hooks/useStyleList.ts @@ -12,18 +12,14 @@ async function fetchStyles() { return data; } - -type StyleStyles = - | string - | { [property: string]: StyleStyles } - | StyleStyles[]; +type StyleStyles = string | { [property: string]: StyleStyles } | StyleStyles[]; interface Style { - id: number, - created: string, - modified: string, - name: string, - styles: StyleStyles, + id: number; + created: string; + modified: string; + name: string; + styles: StyleStyles; } export interface UseStyleListResult { @@ -33,18 +29,17 @@ export interface UseStyleListResult { refetch: Function; } - -function useStyleList():UseStyleListResult { +function useStyleList(): UseStyleListResult { const alert = useAlert(); const history = useHistory(); const queryClient = useQueryClient(); const { data: styles, isLoading, - isError, + isError } = useQuery(['styles'], fetchStyles, { initialData: [], - onError: (err:Error) => { + onError: (err: Error) => { if (err?.name === 'AuthenticationError') { history.push(SIGN_IN); } else { @@ -53,9 +48,14 @@ function useStyleList():UseStyleListResult { } } }); - return { styles, isLoading, isError, refetch: () => { - queryClient.invalidateQueries(['styles']); - }}; + return { + styles, + isLoading, + isError, + refetch: () => { + queryClient.invalidateQueries(['styles']); + } + }; } export default useStyleList; diff --git a/spa/src/styles/defaultTheme.d.ts b/spa/src/styles/defaultTheme.d.ts index d071b7d70f..6fe00017f7 100644 --- a/spa/src/styles/defaultTheme.d.ts +++ b/spa/src/styles/defaultTheme.d.ts @@ -47,6 +47,7 @@ declare module 'styled-components' { 400: string; 500: string; 600: string; + 700: string; 800: string; 900: string; }; @@ -90,24 +91,24 @@ declare module 'styled-components' { font: { body: string; heading: string }; fontSizes: string[]; fontSizesUpdated: { - xs: string; - sm: string; - md: string; - lg: string; - '20': '20px'; - lgx: string; - lg2x: string; - lg3x: string; - h1: string; - xl: string; - '2xl': string; + xs: '12px'; + sm: '14px'; + md: '16px'; + lg: '18px'; + 20: '20px'; + lgx: '24px'; + lg2x: '28px'; + lg3x: '30px'; + h1: '34px'; + xl: '46px'; + '2xl': '72px'; }; muiBorderRadius: { - sm: string; - md: string; - lg: string; - xl: string; - '2xl': string; + sm: '2px'; + md: '4px'; + lg: '6px'; + xl: '10px'; + '2xl': '12px'; }; radii: string[]; shadows: string[]; diff --git a/spa/src/styles/themes.ts b/spa/src/styles/themes.ts index b4c3e7f367..ed57762db1 100644 --- a/spa/src/styles/themes.ts +++ b/spa/src/styles/themes.ts @@ -53,6 +53,7 @@ export const revEngineTheme: DefaultTheme = { 400: '#c4c4c4', 500: '#969696', 600: '#707070', + 700: '#666666', 800: '#3c3c3c', 900: '#282828' },