diff --git a/CHANGELOG.md b/CHANGELOG.md index 289fb7407..7b8029875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Not released +## 2.0 + +### 2.0.0-beta.0 (2023-02-14) +### 2.0.0-alpha.0 (2023-02-13) + +- MUI5 & new Design System upgrade [#494](https://github.com/CartoDB/carto-react/pull/494) + ## 1.5 ### 1.5.1 (2023-02-06) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index d02b2fc9f..86945d8b9 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -47,16 +47,18 @@ You will need npm credentials under @carto organization. 8. Choose `Custom prerelease` and ensure the packages version proposed is correct (eg. change suffix to 'alpha' or 'beta', instead of 'rc' if required) 9. Once the npm package has been published, `Merge the PR` to master from github 10. Update the storybook (if required) +11. Coordinate with the design team on minor and major bump versions. C4R and Design system library must have the same paired version. ### To make an official **release**: + 1. Repeat the same steps as in a prerelease, but executing `yarn publish:release` ### To apply a hotfix patch + - If change also applies to current master, it's recommended to start by creating a PR applying the fix it (to avoid forgetting it). - Then create a branch for the patch release, but this time start with the desired (usually stable) branch. For example, to create a patch 1.4.8, while not affecting current master, do `git checkout -b release-v1.4.8 release-v1.4.7`. Then apply there, locally, all changes as needed. If you created a first PR for master, you can use cherry-pick to share changes among master & patch. - After having everything ready, go as usual, with changelog entry + `yarn publish:release` - ## Firebase deployment of storybook (Restricted to CARTO developers) diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 000000000..1a934e017 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,215 @@ +# Upgrade to the new design system + +# Breaking changes in Mui v5 + +Please, follow the Mui guides related to breaking changes in components and styles: + +- [Styles](https://mui.com/material-ui/migration/v5-style-changes/) +- [Components](https://mui.com/material-ui/migration/v5-component-changes/) + +# MUI theme + +[carto-theme.js](https://github.com/CartoDB/carto-react/blob/master/packages/react-ui/src/theme/carto-theme.js) file splitted in sections: + +- CSS baseline +- Color palette +- Typography +- Shadows +- Components overrides + +Also added some files for shared constants (`themeConstants.js`) and useful functions (`themeUtils.js`). + +Removed unused custom `createTheme` function in `carto-theme.js`. + +## theme.spacing + +We have a new custom spacing constant in carto-theme, `spacingValue`, which you should use instead of the common `theme.spacing()` function in cases where you need to do value calculations, because since Mui v5, theme.spacing is no longer a number, but a string in this format: `number + px`. + +Note that if you're using `calc()` in your styles, you can keep using `theme.spacing()` as usual. + +`theme.spacingValue * 2` + +Needed changes: + +1. Change `${theme.spacing(xx)}px` by `${theme.spacing(xx)}`. It means, without the `px` ending, since in Mui v5 it is appended to the end of the string by default. + +Tip: An easy search to catch up this, would be `)}px` + +2. Change `-theme.spacing(xx)` by `theme.spacing(-xx)`. It means, move the negative symbol inside the function. + +Tip: An easy search to catch up this, would be `-theme.spacing(` + +## Icons + +We have this kind of rules in buttons to cover the common use cases: + +`svg path { fill: currentColor }` + +In case you don't need the icon to be filled, you can apply this class to the svg parent: `.doNotFillIcon` + +`` + +## Typography + +`responsiveFontSizes` simplified due we want to resize only a few variants through the theme. + +Added a few custom variants to the typography set: + +- overlineDelicate +- code1 +- code2 +- code3 + +Some variants have been replaced because they were so specific to some components, these are: + +- charts (replaced by `theme.palette.overline + weight='strong'`) + +For external use: `Open Sans` and `Montserrat` families have been replaced by `Inter` and `Overpass Mono`, you have an example of this in the [`preview-head.html`](https://github.com/CartoDB/carto-react/blob/master/packages/react-ui/storybook/.storybook/preview-head.html) file. + +We have a `Typography` component that uses `Mui Typography` and extends it with some useful props: + +- weight +- italic + +This way we can be more flexible regarding text styles without adding too many variants to the Mui component. + +In short, instead of Mui Typography, the component you should use to add text is this one: +`react-ui/src/components/atoms/Typography` + +For external use: `import { Typography } from '@carto/react-ui';`. + +## Colors + +Some keys have been removed from [color palette](https://github.com/CartoDB/carto-react/tree/master/packages/react-ui/src/theme) due they are unused: + +- activatedOpacity +- hoverOpacity +- disabledOpacity +- selectedOpacity +- focusOpacity +- other, all removed but divider, which is moved to first level + +Some others have been moved or replaced because they aren't native MUI keys and are so specific to some components, these are: + +- charts (replaced by `theme.palette.black[%]`) + +`grey palette` is not used to design (and therefore not for style) components directly. We have a set of neutral colors to use in the custom `default` variant. + +We also have a set of `shade` colors (with transparency): + +- black +- white + +Important: `primary.relatedLight` and `secondary.relatedLight` has to be replaced by `primary.background` and `secondary.background`. + +## Spacing + +Design is restringed to a few specific values for spacing, which are: + +`0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 6, 7, 8, 9, 12, 15`. + +## Shapes + +Design is restringed to a few specific values for border radius, which are: + +`0.5, 1, 1.5, 2`. + +Use: `borderRadius: theme.spacing(x)` + +## Shadows / Elevations + +Design is restringed to a few specific values for shadows, which are: + +`0, 1, 2, 4, 6, 8, 16, 24`. + +# Components + +## Button + +We have a `Button` component that uses `Mui Button` and wraps its children in `Typography` to meet with the designed behavior (text overflow case). + +So, instead of Mui Button, the component you should use to create buttons is this one: +`react-ui/src/components/atoms/Button` + +For external use: `import { Button } from '@carto/react-ui';`. + +## Tooltip + +Now, by default is placed `top` and has an `arrow` indicator, so you don't need to specify these properties anymore. + +We have a new component for building data structures within Tooltips, `TooltipData`. + +## Password Input Field + +We have a custom component to build the show / hide content logic on top of TextField Mui component. + +Instead of ` ` you can use: +`react-ui/src/components/atoms/PasswordField` + +For external use: `import { PasswordField } from '@carto/react-ui';`. + +## Select Field + +We have a custom component to build the `placeholder` and `multiple selection` logic on top of TextField Mui component. + +Instead of ` ` or ` + + ); +} + +UploadField.defaultProps = { + buttonText: 'Browse', + accept: ['application/JSON'], + files: [], + onChange: (files) => files +}; + +UploadField.propTypes = { + placeholder: PropTypes.string, + buttonText: PropTypes.string, + accept: PropTypes.array, + multiple: PropTypes.bool, + error: PropTypes.bool, + files: PropTypes.array, + onChange: PropTypes.func.isRequired, + size: PropTypes.oneOf(['small', 'medium']) +}; + +export default UploadField; diff --git a/packages/react-ui/src/components/organisms/AppBar.js b/packages/react-ui/src/components/organisms/AppBar.js new file mode 100644 index 000000000..b6a20f138 --- /dev/null +++ b/packages/react-ui/src/components/organisms/AppBar.js @@ -0,0 +1,137 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { AppBar as MuiAppBar, Divider, Hidden, IconButton, Toolbar } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { MenuOutlined } from '@mui/icons-material'; +import Typography from '../atoms/Typography'; +import { APPBAR_SIZE } from '../../theme/themeConstants'; + +const useStyles = makeStyles((theme) => ({ + row: { + display: 'flex', + alignItems: 'center', + maxWidth: 'calc(100% - 300px)', + overflow: 'hidden', + + [theme.breakpoints.down('sm')]: { + minWidth: '188px' + } + }, + menu: { + display: 'flex', + alignItems: 'center', + height: APPBAR_SIZE, + marginRight: theme.spacing(1.5) + }, + menuButton: { + marginRight: theme.spacing(1), + + '&.MuiButtonBase-root svg path': { + fill: theme.palette.background.paper + } + }, + logo: { + display: 'flex', + marginRight: theme.spacing(1.5), + + '& a': { + display: 'flex' + }, + '& svg, & img': { + width: theme.spacing(4), + height: theme.spacing(4) + } + }, + content: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + flex: 1, + marginLeft: theme.spacing(1) + }, + text: { + display: 'flex', + alignItems: 'center' + }, + brandText: { + whiteSpace: 'nowrap' + }, + textSeparator: { + '&::after': { + content: '"/"', + margin: theme.spacing(0, 1), + color: theme.palette.white[60] + } + } +})); + +const AppBar = ({ + children, + brandLogo, + brandText, + secondaryText, + showBurgerMenu, + onClickMenu, + ...otherProps +}) => { + const classes = useStyles(); + + return ( + + +
+ {showBurgerMenu && ( + +
+ + + + +
+
+ )} + {brandLogo &&
{brandLogo}
} + {brandText && ( + + {brandText} + + )} + {secondaryText && ( + + {secondaryText} + + )} +
+ +
{children}
+
+
+ ); +}; + +AppBar.defaultProps = { + showBurgerMenu: false +}; + +AppBar.propTypes = { + brandLogo: PropTypes.element, + brandText: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), + secondaryText: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), + onClickMenu: PropTypes.func, + showBurgerMenu: PropTypes.bool +}; + +export default AppBar; diff --git a/packages/react-ui/src/components/organisms/TooltipData.js b/packages/react-ui/src/components/organisms/TooltipData.js new file mode 100644 index 000000000..4efeff6a4 --- /dev/null +++ b/packages/react-ui/src/components/organisms/TooltipData.js @@ -0,0 +1,93 @@ +// TODO: replace widgets Tooltip content with this component +// https://app.shortcut.com/cartoteam/story/272209/replace-widgets-tooltip-with-tooltipdata +import React from 'react'; +import PropTypes from 'prop-types'; +import makeStyles from '@mui/styles/makeStyles'; +import Typography from '../atoms/Typography'; + +const useStyles = makeStyles((theme) => ({ + list: { + listStyle: 'none', + paddingLeft: 0, + margin: theme.spacing(0.5, 0, 0, 0) + }, + item: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + + '&:not(:last-child)': { + marginBottom: theme.spacing(0.5) + } + }, + bullet: { + width: theme.spacing(1), + height: theme.spacing(1), + marginRight: theme.spacing(0.5), + border: `2px solid ${theme.palette.qualitative.bold[1]}`, + borderRadius: '50%', + backgroundColor: theme.palette.qualitative.bold[1] + }, + category: { + minWidth: theme.spacing(10), + marginRight: theme.spacing(1.5) + } +})); + +const TooltipData = ({ items, title }) => { + const classes = useStyles(); + + return ( + <> + {title && ( + + {title} + + )} + +
    + {items.map((item, index) => ( +
  • + + {item.category && ( + + {item.category} + + )} + + {item.value} + +
  • + ))} +
+ + ); +}; + +TooltipData.defaultProps = { + items: [ + { + outlinedBullet: false + } + ] +}; + +TooltipData.propTypes = { + items: PropTypes.arrayOf( + PropTypes.shape({ + category: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + outlinedBullet: PropTypes.bool, + color: PropTypes.string + }) + ).isRequired, + title: PropTypes.string +}; + +export default TooltipData; diff --git a/packages/react-ui/src/custom-components/InputFile.js b/packages/react-ui/src/custom-components/InputFile.js deleted file mode 100644 index 4623e0dc3..000000000 --- a/packages/react-ui/src/custom-components/InputFile.js +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; -import { Box, Button, makeStyles, Typography } from '@material-ui/core'; - -const useStyles = makeStyles((theme) => ({ - inputFile: { - flex: 1, - width: '100%' - }, - inputWrapper: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - flex: 1, - height: theme.spacing(4.5), - marginRight: theme.spacing(1.5), - padding: theme.spacing(0, 1), - border: `2px solid ${theme.palette.action.disabled}`, - borderRadius: theme.spacing(0.5), - transition: theme.transitions.create(['border'], { - duration: theme.transitions.duration.short, - easing: theme.transitions.easing.easeInOut - }) - }, - inputWrapperOver: { - border: `2px solid ${theme.palette.primary.light}` - }, - placeholder: { - opacity: 0.42, - maxWidth: 'calc(100% - 110px)' - }, - placeholderFiles: { - opacity: 1 - } -})); - -function InputFile(props) { - const classes = useStyles(); - const uploadInputRef = useRef(null); - const [filesText, setFilesText] = useState(''); - const [over, setOver] = useState(false); - - useEffect(() => { - if (props.files.length === 0) { - setFilesText(''); - } else if (props.files.length === 1) { - setFilesText(props.files[0].name); - } else { - setFilesText(`${props.files.length} files selected`); - } - }, [props.files]); - - const handleBrowse = () => { - uploadInputRef.current?.click(); - }; - - const handleDragOver = (event) => { - event.preventDefault(); - setOver(true); - }; - - const handleDragLeave = (event) => { - event.preventDefault(); - setOver(false); - }; - - const handleDrop = (event) => { - event.preventDefault(); - - const items = event.dataTransfer.items; - const newFiles = getAllFiles(items); - props.onChange(newFiles); - }; - - const getAllFiles = (items) => { - const newFiles = []; - for (let item of items) { - if (item.kind === 'file') { - const file = item.getAsFile(); - newFiles.push(file); - } - } - - return newFiles; - }; - - const handleFiles = (event) => { - const newFiles = Array.from(event.target.files); - props.onChange(newFiles); - }; - - return ( - - - {filesText || props.placeholder} - - - - - ); -} - -InputFile.defaultProps = { - placeholder: 'Drag and drop your file or click to browse', - buttonText: 'Browse', - accept: 'application/JSON', - multiple: false, - files: [], - onChange: (files) => files -}; - -InputFile.propTypes = { - placeholder: PropTypes.string, - buttonText: PropTypes.string, - accept: PropTypes.string, - multiple: PropTypes.bool, - files: PropTypes.array, - onChange: PropTypes.func.isRequired -}; - -export default InputFile; diff --git a/packages/react-ui/src/hooks/useAnimatedNumber.js b/packages/react-ui/src/hooks/useAnimatedNumber.js index 47b2c357b..504ef9420 100644 --- a/packages/react-ui/src/hooks/useAnimatedNumber.js +++ b/packages/react-ui/src/hooks/useAnimatedNumber.js @@ -1,19 +1,21 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from 'react'; import { animateValue } from '../widgets/utils/animations'; /** * React hook to handle animating value changes over time, abstracting the necesary state, refs and effects - * @param {number} value - * @param {{ disabled?: boolean; duration?: number; animateOnMount?: boolean; initialValue?: number; }} [options] + * @param {number} value + * @param {{ disabled?: boolean; duration?: number; animateOnMount?: boolean; initialValue?: number; }} [options] */ export default function useAnimatedNumber(value, options = {}) { const { disabled, duration, animateOnMount, initialValue = 0 } = options; /** @type {any} */ - const requestAnimationFrameRef = useRef(); + const requestAnimationFrameRef = useRef(); // if we want to run the animation on mount, we set the starting value of the animated number as 0 (or the number in `initialValue`) and animate to the target value from there - const [animatedValue, setAnimatedValue] = useState(() => animateOnMount ? initialValue : value); + const [animatedValue, setAnimatedValue] = useState(() => + animateOnMount ? initialValue : value + ); useEffect(() => { if (!disabled) { @@ -25,15 +27,15 @@ export default function useAnimatedNumber(value, options = {}) { requestRef: requestAnimationFrameRef }); } else { - setAnimatedValue(value) + setAnimatedValue(value); } return () => { // eslint-disable-next-line react-hooks/exhaustive-deps cancelAnimationFrame(requestAnimationFrameRef.current); - } + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [value, disabled, duration]); return animatedValue; -}; +} diff --git a/packages/react-ui/src/index.d.ts b/packages/react-ui/src/index.d.ts index deeed4e76..83c141170 100644 --- a/packages/react-ui/src/index.d.ts +++ b/packages/react-ui/src/index.d.ts @@ -1,4 +1,4 @@ -import { cartoThemeOptions, CartoTheme } from './theme/carto-theme'; +import { theme, cartoThemeOptions, CartoTheme } from './theme/carto-theme'; import WrapperWidgetUI from './widgets/WrapperWidgetUI'; import CategoryWidgetUI from './widgets/CategoryWidgetUI'; import FormulaWidgetUI from './widgets/FormulaWidgetUI'; @@ -25,8 +25,15 @@ import FeatureSelectionWidgetUI from './widgets/FeatureSelectionWidgetUI'; import ComparativePieWidgetUI from './widgets/comparative/ComparativePieWidgetUI'; import ComparativeFormulaWidgetUI from './widgets/comparative/ComparativeFormulaWidgetUI'; import ComparativeCategoryWidgetUI from './widgets/comparative/ComparativeCategoryWidgetUI/ComparativeCategoryWidgetUI'; +import Typography from './components/atoms/Typography'; +import Button from './components/atoms/Button'; +import PasswordField from './components/atoms/PasswordField'; +import SelectField from './components/atoms/SelectField'; +import UploadField from './components/molecules/UploadField'; +import AppBar from './components/organisms/AppBar'; export { + theme, cartoThemeOptions, CartoTheme, WrapperWidgetUI, @@ -53,5 +60,11 @@ export { LegendCategories, LegendIcon, LegendProportion, - LegendRamp + LegendRamp, + Typography, + Button, + PasswordField, + SelectField, + UploadField, + AppBar }; diff --git a/packages/react-ui/src/index.js b/packages/react-ui/src/index.js index a7f4f08e1..624d2a763 100644 --- a/packages/react-ui/src/index.js +++ b/packages/react-ui/src/index.js @@ -1,4 +1,4 @@ -import { cartoThemeOptions } from './theme/carto-theme'; +import { theme, cartoThemeOptions } from './theme/carto-theme'; import WrapperWidgetUI from './widgets/WrapperWidgetUI'; import CategoryWidgetUI from './widgets/CategoryWidgetUI'; import FormulaWidgetUI from './widgets/FormulaWidgetUI'; @@ -19,12 +19,19 @@ import ComparativeCategoryWidgetUI from './widgets/comparative/ComparativeCatego import { CHART_TYPES } from './widgets/TimeSeriesWidgetUI/utils/constants'; import TableWidgetUI from './widgets/TableWidgetUI/TableWidgetUI'; import NoDataAlert from './widgets/NoDataAlert'; +import ComparativePieWidgetUI from './widgets/comparative/ComparativePieWidgetUI'; import CursorIcon from './assets/CursorIcon'; import PolygonIcon from './assets/PolygonIcon'; import RectangleIcon from './assets/RectangleIcon'; import LassoIcon from './assets/LassoIcon'; import CircleIcon from './assets/CircleIcon'; -import ComparativePieWidgetUI from './widgets/comparative/ComparativePieWidgetUI'; +import ArrowDropIcon from './assets/ArrowDropIcon'; +import Typography from './components/atoms/Typography'; +import Button from './components/atoms/Button'; +import PasswordField from './components/atoms/PasswordField'; +import SelectField from './components/atoms/SelectField'; +import UploadField from './components/molecules/UploadField'; +import AppBar from './components/organisms/AppBar'; const featureSelectionIcons = { CursorIcon, @@ -35,6 +42,7 @@ const featureSelectionIcons = { }; export { + theme, cartoThemeOptions, WrapperWidgetUI, CategoryWidgetUI, @@ -58,5 +66,12 @@ export { LegendCategories, LegendIcon, LegendProportion, - LegendRamp + LegendRamp, + Typography, + Button, + PasswordField, + SelectField, + UploadField, + AppBar, + ArrowDropIcon }; diff --git a/packages/react-ui/src/theme/carto-theme.d.ts b/packages/react-ui/src/theme/carto-theme.d.ts index a911c981a..3ec9eb251 100644 --- a/packages/react-ui/src/theme/carto-theme.d.ts +++ b/packages/react-ui/src/theme/carto-theme.d.ts @@ -1,19 +1,138 @@ -import { Theme, ThemeOptions } from '@material-ui/core'; -import { Palette, PaletteColor } from '@material-ui/core/styles/createPalette'; +import { Theme, ThemeOptions, TypeText } from '@mui/material'; +import { Palette, PaletteColor } from '@mui/material'; export const cartoThemeOptions: ThemeOptions; +export const theme: CartoTheme; -type Modify = Omit & R -type CustomPaletteColor = Modify -type CustomPalette = Modify - -export type CartoTheme = Modify +type Modify = Omit & R; + +type CustomMainPaletteColor = Modify< + PaletteColor, + { + relatedLight: string; + background: string; + } +>; +type CustomFeedbackPaletteColor = Modify< + PaletteColor, + { + relatedDark: string; + relatedLight: string; + } +>; +type CustomDefaultPaletteColor = Modify< + PaletteColor, + { + background: string; + outlinedBorder: string; + } +>; +type CustomBrandPaletteColor = { + navyBlue: string; + locationRed: string; + predictionBlue: string; + softBlue: string; +}; +type CustomTextPaletteColor = Modify< + TypeText, + { + hint: string; + } +>; +type CustomShadesPaletteColor = { + 90: string; + 60: string; + 40: string; + 25: string; + 12: string; + 8: string; + 4: string; +}; + +type CustomPalette = Modify< + Palette, + { + primary: CustomMainPaletteColor; + secondary: CustomMainPaletteColor; + error: CustomFeedbackPaletteColor; + warning: CustomFeedbackPaletteColor; + info: CustomFeedbackPaletteColor; + success: CustomFeedbackPaletteColor; + default: CustomDefaultPaletteColor; + text: CustomTextPaletteColor; + white: CustomShadesPaletteColor; + black: CustomShadesPaletteColor; + brand: CustomBrandPaletteColor; + } +>; + +// If we get every client to use 'default' theme types, module augmentation probably would allow us omiting this export +export type CartoTheme = Modify< + Theme, + { + palette: CustomPalette; + spacingValue: number; + } +>; + +/* + Module augmentation, to allow a better experience when using carto-react from + TypeScript clients (eg: *makeStyles* using 'DefaultTheme' | *useTheme* using 'Theme') +*/ + +declare module '@mui/styles/defaultTheme' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface DefaultTheme extends CartoTheme {} +} + +declare module '@mui/material/styles' { + // Check https://mui.com/material-ui/customization/theming/#custom-variables + interface Theme { + palette: CustomPalette; + spacingValue: number; + } + + // allow configuration using `createTheme` + interface ThemeOptions { + palette?: CustomPalette; + spacingValue?: number; + } + + interface TypographyVariants { + overlineDelicate: React.CSSProperties; + code1: React.CSSProperties; + code2: React.CSSProperties; + code3: React.CSSProperties; + } + + interface TypographyVariantsOptions { + overlineDelicate?: React.CSSProperties; + code1?: React.CSSProperties; + code2?: React.CSSProperties; + code3?: React.CSSProperties; + } +} + +// Update the Typography's variant prop options +declare module '@mui/material/Typography' { + interface TypographyPropsVariantOverrides { + overlineDelicate: true; + code1: true; + code2: true; + code3: true; + } +} + +// Update the Buttons's color prop options +declare module '@mui/material/Button' { + interface ButtonPropsColorOverrides { + default: true; + } +} + +// Update the FAB's color prop options +declare module '@mui/material/Fab' { + interface FabPropsColorOverrides { + default: true; + } +} diff --git a/packages/react-ui/src/theme/carto-theme.js b/packages/react-ui/src/theme/carto-theme.js index 52a510bc9..5e6493a63 100644 --- a/packages/react-ui/src/theme/carto-theme.js +++ b/packages/react-ui/src/theme/carto-theme.js @@ -1,303 +1,31 @@ -import { - createTheme as createMuiTheme, - responsiveFontSizes -} from '@material-ui/core/styles'; -import createSpacing from '@material-ui/core/styles/createSpacing'; - -const colors = { - common: { - black: '#2c3032', - white: '#fff' - }, - neutral: { - 50: '#f8f9f9', - 100: '#e1e3e4', - 200: '#cbcdcf', - 300: '#b4b8ba', - 400: '#9da2a6', - 500: '#868d91', - 600: '#6f777c', - 700: '#595f63', - 800: '#43474a', - 900: '#2c3032', - A100: '#ddddde', - A200: '#b9babb', - A400: '#7c7e7f', - A700: '#545759' - }, - qualitative: { - bold: { - 0: '#7F3C8D', - 1: '#11A579', - 2: '#3969AC', - 3: '#F2B701', - 4: '#E73F74', - 5: '#80BA5A', - 6: '#E68310', - 7: '#008695', - 8: '#CF1C90', - 9: '#f97b72', - 10: '#4b4b8f', - 11: '#A5AA99' - } - }, - shades: { - dark: { - 100: '#2c3032', // Neutral900 - 90: 'rgba(44, 48, 50, 0.9)', - 60: 'rgba(44, 48, 50, 0.6)', - 40: 'rgba(44, 48, 50, 0.4)', - 25: 'rgba(44, 48, 50, 0.25)', - 12: 'rgba(44, 48, 50, 0.12)', - 5: 'rgba(44, 48, 50, 0.05)' - }, - light: { - 100: '#fff', // White - 60: 'rgba(255, 255, 255, 0.6)', - 40: 'rgba(255, 255, 255, 0.4)', - 20: 'rgba(255, 255, 255, 0.2)', - 12: 'rgba(255, 255, 255, 0.12)', - 5: 'rgba(255, 255, 255, 0.05)' - } - } -}; - -const variables = { - palette: { - type: 'light', - common: { ...colors.common }, - primary: { - light: '#358be7', - main: '#036fe2', - dark: '#024d9e', - contrastText: colors.common.white, - relatedLight: 'rgba(3, 111, 226, 0.08)' - }, - secondary: { - light: '#6be2ad', - main: '#47db99', - dark: '#31996b', - contrastText: colors.common.black, - relatedLight: 'rgba(71, 219, 153, 0.08)' - }, - error: { - light: '#cd593b', - main: '#c1300b', - dark: '#872107', - contrastText: colors.common.white, - relatedDark: '#4d1304', - relatedLight: '#f9ebe7' - }, - warning: { - light: '#f4b134', - main: '#f29e02', - dark: '#a96e01', - contrastText: colors.common.black, - relatedDark: '#603f00', - relatedLight: '#fef6e6' - }, - info: { - light: '#34689f', - main: '#024388', - dark: '#012e5f', - contrastText: colors.common.white, - relatedDark: '#001a36', - relatedLight: '#e6edf4' - }, - success: { - light: '#8cb24a', - main: '#709f1d', - dark: '#4e6f14', - contrastText: colors.common.white, - relatedDark: '#2c3f0b', - relatedLight: '#f1f6e9' - }, - text: { - primary: colors.shades.dark[100], - secondary: colors.shades.dark[60], - hint: colors.shades.dark[40], - disabled: colors.shades.dark[25] - }, - background: { - default: colors.neutral[50], - paper: colors.common.white - }, - other: { - tooltip: colors.shades.dark[90], - snackbar: colors.shades.dark[100], - backdrop: colors.shades.dark[60], - divider: colors.shades.dark[12] - }, - grey: { - ...colors.neutral - }, - action: { - active: colors.shades.dark[40], - hover: colors.shades.dark[5], - hoverOpacity: 0.08, - selected: colors.shades.dark[12], - selectedOpacity: 0.08, - disabled: colors.shades.dark[25], - disabledBackground: colors.shades.dark[12], - disabledOpacity: 0.38, - focus: colors.shades.dark[12], - focusOpacity: 0.12, - activatedOpacity: 0.12 - }, - qualitative: { - ...colors.qualitative - } - }, - - typography: { - htmlFontSize: 16, - fontFamily: 'Montserrat, sans-serif', - fontSize: 16, - lineHeight: 1.5, - fontWeightLight: 300, - fontWeightRegular: 400, - fontWeightMedium: 500, - fontWeightBold: 600, - fontSmoothing: 'antialiased', - h1: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '6rem', - lineHeight: 1.084, - letterSpacing: '-0.016em', - fontSmoothing: 'antialiased' - }, - h2: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '4rem', - lineHeight: 1.125, - letterSpacing: '-0.008em', - fontSmoothing: 'antialiased' - }, - h3: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '3rem', - lineHeight: 1.167, - letterSpacing: '0em', - fontSmoothing: 'antialiased' - }, - h4: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '2.125rem', - lineHeight: 1.177, - letterSpacing: '0.007em', - fontSmoothing: 'antialiased' - }, - h5: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '1.5rem', - lineHeight: 1.334, - letterSpacing: '0em', - fontSmoothing: 'antialiased' - }, - h6: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '1.25rem', - lineHeight: 1.2, - letterSpacing: '0.007em', - fontSmoothing: 'antialiased' - }, - subtitle1: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '1rem', - lineHeight: 1.5, - letterSpacing: '0.009em', - fontSmoothing: 'antialiased' - }, - subtitle2: { - fontFamily: '"Open Sans", sans-serif', - fontWeight: 600, - fontSize: '0.875rem', - lineHeight: 1.715, - letterSpacing: '0.007em', - fontSmoothing: 'antialiased' - }, - body1: { - fontFamily: '"Open Sans", sans-serif', - fontWeight: 400, - fontSize: '1rem', - lineHeight: 1.5, - letterSpacing: '0.028em', - fontSmoothing: 'antialiased' - }, - body2: { - fontFamily: '"Open Sans", sans-serif', - fontWeight: 400, - fontSize: '0.875rem', - lineHeight: 1.429, - letterSpacing: '0.018em', - fontSmoothing: 'antialiased' - }, - button: { - fontFamily: 'Montserrat, sans-serif', - fontWeight: 600, - fontSize: '0.875rem', - lineHeight: 1.715, - letterSpacing: '0.018em', - textTransform: 'inherit', - fontSmoothing: 'antialiased' - }, - caption: { - fontFamily: '"Open Sans", sans-serif', - fontWeight: 600, - fontSize: '0.75rem', - lineHeight: 1.334, - letterSpacing: '0.017em', - fontSmoothing: 'antialiased' - }, - overline: { - fontFamily: '"Open Sans", sans-serif', - fontWeight: 400, - fontSize: '0.625rem', - lineHeight: 1.6, - letterSpacing: '0.150em', - textTransform: 'uppercase', - fontSmoothing: 'antialiased' - }, - charts: { - fontFamily: '"Open Sans", sans-serif', - fontWeight: 400, - fontSize: 10, - lineHeight: 1.6, - letterSpacing: '0.150em', - fontSmoothing: 'antialiased' - } - }, - spacing: 8 -}; - -const spacing = createSpacing(variables.spacing); -const round = (value) => Math.round(value * 1e5) / 1e5; -const pxToRem = (size) => `${round(size / variables.typography.htmlFontSize)}rem`; - -variables.typography.pxToRem = pxToRem; -variables.typography.round = round; +import { createTheme, responsiveFontSizes } from '@mui/material'; + +import { dataDisplayOverrides } from './sections/components/dataDisplay'; +import { buttonsOverrides } from './sections/components/buttons'; +import { formsOverrides } from './sections/components/forms'; +import { navigationOverrides } from './sections/components/navigation'; +import { CssBaseline } from './sections/cssBaseline'; +import { commonPalette } from './sections/palette'; +import { themeShadows } from './sections/shadows'; +import { themeTypography } from './sections/typography'; +import { feedbackOverrides } from './sections/components/feedback'; +import { surfacesOverrides } from './sections/components/surfaces'; +import { BREAKPOINTS, SPACING } from './themeConstants'; export const cartoThemeOptions = { themeName: 'CARTO', breakpoints: { keys: ['xs', 'sm', 'md', 'lg', 'xl'], values: { - xs: 0, - sm: 600, - md: 960, - lg: 1280, - xl: 1920 + xs: BREAKPOINTS.XS, // 320 - 599 + sm: BREAKPOINTS.SM, // 600 - 959 + md: BREAKPOINTS.MD, // 960 - 1279 + lg: BREAKPOINTS.LG, // 1280 - 1599 + xl: BREAKPOINTS.XL // 1600+ }, unit: 'px', - tep: 5 - // For more information about use this helper functions: https://material-ui.com/customization/spacing/#custom-spacing + step: 5 + // For more information about use this helper functions: https://mui.com/material-ui/customization/breakpoints/#css-media-queries // up: f d(), // down: f down(), // between: f p(), @@ -318,63 +46,35 @@ export const cartoThemeOptions = { } }, palette: { - type: 'light', - common: { ...variables.palette.common }, - primary: { ...variables.palette.primary }, - secondary: { ...variables.palette.secondary }, - error: { ...variables.palette.error }, - warning: { ...variables.palette.warning }, - info: { ...variables.palette.info }, - success: { ...variables.palette.success }, + mode: 'light', contrastThreshold: 3, tonalOffset: 0.2, - text: { ...variables.palette.text }, - divider: 'rgba(0, 0, 0, 0.12)', - other: { ...variables.palette.other }, - background: { ...variables.palette.background }, - charts: { - axisLine: variables.palette.action.hover, - maxLabel: variables.palette.text.secondary, - disabled: variables.palette.text.disabled, - axisPointer: colors.shades.dark[40] - }, + common: { ...commonPalette.common }, + primary: { ...commonPalette.primary }, + secondary: { ...commonPalette.secondary }, + error: { ...commonPalette.error }, + warning: { ...commonPalette.warning }, + info: { ...commonPalette.info }, + success: { ...commonPalette.success }, + text: { ...commonPalette.text }, + divider: commonPalette.divider, + background: { ...commonPalette.background }, + grey: { ...commonPalette.grey }, + action: { ...commonPalette.action }, // props: Object => Research, /* Custom Colors palette */ - grey: { ...variables.palette.grey }, - action: { ...variables.palette.action }, - qualitative: { ...variables.palette.qualitative } + qualitative: { ...commonPalette.qualitative }, + default: { ...commonPalette.default }, + white: { ...commonPalette.white }, + black: { ...commonPalette.black }, + brand: { ...commonPalette.brand } }, - shadows: [ - 'none', - '0px 2px 1px -1px rgba(0,0,0,0.16),0px 1px 1px 0px rgba(0,0,0,0.08),0px 1px 3px 0px rgba(0,0,0,0.04)', - '0px 3px 1px -2px rgba(0,0,0,0.16),0px 2px 2px 0px rgba(0,0,0,0.08),0px 1px 5px 0px rgba(0,0,0,0.04)', - '0px 3px 3px -2px rgba(0,0,0,0.16),0px 3px 4px 0px rgba(0,0,0,0.08),0px 1px 8px 0px rgba(0,0,0,0.04)', - '0px 2px 4px -1px rgba(0,0,0,0.16),0px 4px 5px 0px rgba(0,0,0,0.08),0px 1px 10px 0px rgba(0,0,0,0.04)', - '0px 3px 5px -1px rgba(0,0,0,0.16),0px 5px 8px 0px rgba(0,0,0,0.08),0px 1px 14px 0px rgba(0,0,0,0.04)', - '0px 3px 5px -1px rgba(0,0,0,0.16),0px 6px 10px 0px rgba(0,0,0,0.08),0px 1px 18px 0px rgba(0,0,0,0.04)', - '0px 4px 5px -2px rgba(0,0,0,0.16),0px 7px 10px 1px rgba(0,0,0,0.08),0px 2px 16px 1px rgba(0,0,0,0.04)', - '0px 5px 5px -3px rgba(0,0,0,0.16),0px 8px 10px 1px rgba(0,0,0,0.08),0px 3px 14px 2px rgba(0,0,0,0.04)', - '0px 5px 6px -3px rgba(0,0,0,0.16),0px 9px 12px 1px rgba(0,0,0,0.08),0px 3px 16px 2px rgba(0,0,0,0.04)', - '0px 6px 6px -3px rgba(0,0,0,0.16),0px 10px 14px 1px rgba(0,0,0,0.08),0px 4px 18px 3px rgba(0,0,0,0.04)', - '0px 6px 7px -4px rgba(0,0,0,0.16),0px 11px 15px 1px rgba(0,0,0,0.08),0px 4px 20px 3px rgba(0,0,0,0.04)', - '0px 7px 8px -4px rgba(0,0,0,0.16),0px 12px 17px 2px rgba(0,0,0,0.08),0px 5px 22px 4px rgba(0,0,0,0.04)', - '0px 7px 8px -4px rgba(0,0,0,0.16),0px 13px 19px 2px rgba(0,0,0,0.08),0px 5px 24px 4px rgba(0,0,0,0.04)', - '0px 7px 9px -4px rgba(0,0,0,0.16),0px 14px 21px 2px rgba(0,0,0,0.08),0px 5px 26px 4px rgba(0,0,0,0.04)', - '0px 8px 9px -5px rgba(0,0,0,0.16),0px 15px 22px 2px rgba(0,0,0,0.08),0px 6px 28px 5px rgba(0,0,0,0.04)', - '0px 8px 10px -5px rgba(0,0,0,0.16),0px 16px 24px 2px rgba(0,0,0,0.08),0px 6px 30px 5px rgba(0,0,0,0.04)', - '0px 8px 11px -5px rgba(0,0,0,0.16),0px 17px 26px 2px rgba(0,0,0,0.08),0px 6px 32px 5px rgba(0,0,0,0.04)', - '0px 9px 11px -5px rgba(0,0,0,0.16),0px 18px 28px 2px rgba(0,0,0,0.08),0px 7px 34px 6px rgba(0,0,0,0.04)', - '0px 9px 12px -6px rgba(0,0,0,0.16),0px 19px 29px 2px rgba(0,0,0,0.08),0px 7px 36px 6px rgba(0,0,0,0.04)', - '0px 10px 13px -6px rgba(0,0,0,0.16),0px 20px 31px 3px rgba(0,0,0,0.08),0px 8px 38px 7px rgba(0,0,0,0.04)', - '0px 10px 13px -6px rgba(0,0,0,0.16),0px 21px 33px 3px rgba(0,0,0,0.08),0px 8px 40px 7px rgba(0,0,0,0.04)', - '0px 10px 14px -6px rgba(0,0,0,0.16),0px 22px 35px 3px rgba(0,0,0,0.08),0px 8px 42px 7px rgba(0,0,0,0.04)', - '0px 11px 14px -7px rgba(0,0,0,0.16),0px 23px 36px 3px rgba(0,0,0,0.08),0px 9px 44px 8px rgba(0,0,0,0.04)', - '0px 11px 15px -7px rgba(0,0,0,0.16),0px 24px 38px 3px rgba(0,0,0,0.08),0px 9px 46px 8px rgba(0,0,0,0.04)' - ], + shadows: [...themeShadows], typography: { - ...variables.typography + ...themeTypography }, - spacing: variables.spacing, // For custom spacing: https://material-ui.com/customization/spacing/#custom-spacing + spacingValue: SPACING, // For situations where we can't use theme.spacing(), mainly math calculations. + spacing: SPACING, // For custom spacing: https://material-ui.com/customization/spacing/#custom-spacing shape: { borderRadius: 4 }, @@ -406,1024 +106,22 @@ export const cartoThemeOptions = { snackbar: 1400, tooltip: 1500 }, - overrides: { - MuiCssBaseline: { - '@global': { - // Custom scrollbars - '*::-webkit-scrollbar': { - position: 'fixed', - width: '5px' - }, - '*::-webkit-scrollbar-track': { - '-webkit-box-shadow': 'none', - background: 'transparent' - }, - '*::-webkit-scrollbar-thumb': { - borderRadius: '3px', - background: variables.palette.action.focus, - outline: 'none' - }, - - // iOS Search clear button - 'input[type="search"]::-webkit-search-cancel-button': { - '-webkit-appearance': 'none', - height: spacing(2), - width: spacing(2), - display: 'block', - backgroundImage: `url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAn0lEQVR42u3UMQrDMBBEUZ9WfQqDmm22EaTyjRMHAlM5K+Y7lb0wnUZPIKHlnutOa+25Z4D++MRBX98MD1V/trSppLKHqj9TTBWKcoUqffbUcbBBEhTjBOV4ja4l4OIAZThEOV6jHO8ARXD+gPPvKMABinGOrnu6gTNUawrcQKNCAQ7QeTxORzle3+sDfjJpPCqhJh7GixZq4rHcc9l5A9qZ+WeBhgEuAAAAAElFTkSuQmCC)`, - backgroundRepeat: 'no-repeat', - backgroundSize: spacing(2) - }, - - // Mapbox controls - '.mapboxgl-ctrl.mapboxgl-ctrl-attrib': { - padding: spacing(0, 1), - borderRadius: spacing(0.5, 0, 0, 0), - - '& .mapboxgl-ctrl-attrib-inner': { - ...variables.typography.overline, - textTransform: 'none', - letterSpacing: '0.75px', - - '& a': { - color: variables.palette.primary.main - } - }, - - '&.mapboxgl-compact': { - backgroundColor: 'transparent', - right: spacing(0.5), - bottom: spacing(2.5), - - // Mobile - '@media (max-width: 600px)': { - bottom: spacing(0.5) - }, - - '& .mapboxgl-ctrl-attrib-button': { - backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20height='24'%20viewBox='0%200%2024%2024'%20width='24'%3E%3Cg%3E%3Crect%20fill='none'%20height='24'%20width='24'%20x='0'/%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Cg%3E%3Cpath%20d='M11.88,9.14c1.28,0.06,1.61,1.15,1.63,1.66h1.79c-0.08-1.98-1.49-3.19-3.45-3.19C9.64,7.61,8,9,8,12.14%20c0,1.94,0.93,4.24,3.84,4.24c2.22,0,3.41-1.65,3.44-2.95h-1.79c-0.03,0.59-0.45,1.38-1.63,1.44C10.55,14.83,10,13.81,10,12.14%20C10,9.25,11.28,9.16,11.88,9.14z%20M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z%20M12,20c-4.41,0-8-3.59-8-8%20s3.59-8,8-8s8,3.59,8,8S16.41,20,12,20z'%20fill='${variables.palette.text.secondary}'/%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`, - backgroundColor: 'rgba(255,255,255,.8)', - top: 'auto', - bottom: 0, - right: 0, - - '&:not(:disabled):hover': { - backgroundColor: 'rgba(255,255,255,.8)' - } - }, - - '& .mapboxgl-ctrl-attrib-inner': { - backgroundColor: 'rgba(255,255,255,.8)', - padding: spacing(0.5, 1), - borderRadius: spacing(1.5), - marginRight: spacing(2.5), - color: variables.palette.text.secondary - }, - - '&.mapboxgl-compact-show': { - '& .mapboxgl-ctrl-attrib-button': { - backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20height='24'%20viewBox='0%200%2024%2024'%20width='24'%3E%3Cpath%20d='M0%200h24v24H0z'%20fill='none'/%3E%3Cpath%20d='M19%206.41L17.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z'%20fill='white'/%3E%3C/svg%3E")`, - backgroundColor: variables.palette.common.black, - - '&:not(:disabled):hover': { - backgroundColor: variables.palette.common.black - } - } - } - } - } - } - }, - - // Button - MuiButton: { - contained: { - boxShadow: 'none' - }, - outlined: { - border: `2px solid ${variables.palette.text.primary}`, - padding: '4px 14px', - '&:hover': { - borderWidth: '2px' - }, - '&$disabled': { - borderWidth: '2px' - } - }, - outlinedPrimary: { - border: `2px solid ${variables.palette.primary.main}`, - '&:hover': { - borderWidth: '2px' - } - }, - outlinedSecondary: { - border: `2px solid ${variables.palette.secondary.main}`, - '&:hover': { - borderWidth: '2px' - }, - '&$disabled': { - borderWidth: '2px' - } - }, - containedSizeSmall: { - padding: '2px 12px', - fontSize: pxToRem(12) - }, - outlinedSizeSmall: { - padding: '2px 12px', - fontSize: pxToRem(12) - }, - textSizeSmall: { - padding: '2px 12px', - fontSize: pxToRem(12) - }, - containedSizeLarge: { - padding: '16px 24px', - fontSize: pxToRem(16) - }, - containedSecondary: { - '&:hover': { - backgroundColor: variables.palette.secondary.light - } - }, - outlinedSizeLarge: { - padding: '16px 24px', - fontSize: pxToRem(16) - }, - textSizeLarge: { - padding: '16px 24px', - fontSize: pxToRem(16) - }, - startIcon: { - marginRight: 6, - marginLeft: -4, - '&$iconSizeSmall': { - marginLeft: -4 - }, - '&$iconSizeLarge': { - marginRight: 8 - } - }, - endIcon: { - marginRight: -4, - marginLeft: 6, - '&$iconSizeSmall': { - marginRight: -4 - }, - '&$iconSizeLarge': { - marginLeft: 8 - } - }, - iconSizeSmall: { - '& > *:first-child': { - fontSize: 20 - } - }, - iconSizeMedium: { - '& > *:first-child': { - fontSize: 24 - } - }, - iconSizeLarge: { - '& > *:first-child': { - fontSize: 24 - } - } - }, - MuiIconButton: { - root: { - padding: spacing(0.75), - borderRadius: spacing(0.5), - color: variables.palette.text.primary - }, - sizeSmall: { - padding: spacing(0.25) - } - }, - - MuiInputBase: { - root: { - '&$disabled .MuiInputAdornment-root': { - color: variables.palette.action.disabled - }, - '&$disabled .MuiTypography-root': { - color: variables.palette.action.disabled - } - } - }, - MuiOutlinedInput: { - root: { - '&$disabled': { - backgroundColor: variables.palette.action.hover - } - }, - - input: { - ...variables.typography.body1, - height: `${variables.typography.body1.lineHeight}em`, - padding: spacing(3, 2, 1) - }, - - inputMarginDense: { - ...variables.typography.body2, - height: `${variables.typography.body2.lineHeight}em`, - padding: spacing(1, 1.5), - paddingTop: spacing(1), - paddingBottom: spacing(1) - }, - - adornedStart: { - '&$marginDense': { - paddingLeft: spacing(1.5) - } - }, - adornedEnd: { - '&$marginDense': { - paddingRight: spacing(1.5) - } - }, - - notchedOutline: { - border: `2px solid ${variables.palette.text.disabled}` - }, - - multiline: { - padding: spacing(2.75, 2, 1.25) - } - }, - MuiInputLabel: { - root: { - ...variables.typography.body1 - }, - - formControl: { - transform: 'translate(16px, 20px) scale(1)', - - '&$shrink': { - ...variables.typography.caption, - transform: 'translate(16px, 8px) scale(1)' - }, - - '&$marginDense': { - ...variables.typography.caption, - transform: 'translate(0, -20px) scale(1)', - - '&$shrink': { - ...variables.typography.caption, - transform: 'translate(0, -20px) scale(1)' - } - } - }, - - outlined: { - '&$shrink': { - ...variables.typography.caption, - transform: 'translate(16px, 8px) scale(1)' - }, - - '&$marginDense': { - ...variables.typography.caption, - transform: 'translate(0, -20px) scale(1)', - - '&$shrink': { - transform: 'translate(0, -20px) scale(1)' - } - } - } - }, - MuiInputAdornment: { - root: { - ...variables.typography.body1, - alignItems: 'baseline', - marginBottom: spacing(1.5), - color: variables.palette.text.secondary, - - '&:disabled': { - color: variables.palette.action.disabled - }, - - '& .MuiSvgIcon-root': { - fontSize: `${variables.typography.body1.lineHeight}em` - } - }, - - positionStart: { - marginLeft: spacing(0.25) - }, - - positionEnd: { - marginRight: spacing(0.25) - }, - - marginDense: { - marginBottom: spacing(0), - alignItems: 'center', - ...variables.typography.body2, - - '& .MuiTypography-root': { - ...variables.typography.body2 - }, - - '& .MuiSvgIcon-root': { - fontSize: `${variables.typography.body2.lineHeight}em` - } - } - }, - MuiFormHelperText: { - root: { - ...variables.typography.caption, - '&$contained': { - marginTop: spacing(1) - } - }, - - marginDense: { - '&$contained': { - marginLeft: spacing(0) - } - } - }, - - // Select - MuiFormControl: { - root: { - width: '100%' - } - }, - MuiSelect: { - selectMenu: {}, - - root: { - '&:hover': { - backgroundColor: 'transparent' - } - }, - - select: { - '&:focus': { - backgroundColor: 'transparent' - } - } - }, - - // Menu - MuiMenuItem: { - root: { - ...variables.typography.body2 - } - }, - - // Autocomplete - MuiAutocomplete: { - inputRoot: { - '&[class*="MuiOutlinedInput-root"]': { - padding: spacing(3, 1.25, 0.5), - - '& .MuiAutocomplete-input': { - padding: spacing(0, 1.25, 0.5) - } - }, - '&.MuiInputBase-marginDense.MuiOutlinedInput-root $input.MuiOutlinedInput-inputMarginDense': - { - paddingTop: spacing(0.25), - paddingBottom: spacing(0.25) - } - } - }, - - // Checkbox - MuiCheckbox: { - root: { - ...variables.typography.body2, - padding: spacing(0.75), - borderRadius: '50%', - - '& + .MuiFormControlLabel-label': { - ...variables.typography.body2, - marginLeft: spacing(0.25), - color: variables.palette.text.primary - }, - - '& .MuiSvgIcon-root': { - fontSize: spacing(3) - } - } - }, - - // RadioButton - MuiRadio: { - root: { - ...variables.typography.body2, - padding: spacing(0.75), - borderRadius: '50%', - - '& + .MuiFormControlLabel-label': { - ...variables.typography.body2, - marginLeft: spacing(0.25), - color: variables.palette.text.primary - }, - - '& .MuiSvgIcon-root': { - fontSize: spacing(3) - } - } - }, - - // Tabs - MuiTabs: { - indicator: { - height: 4, - '&.colorPrimary': { - backgroundColor: variables.palette.text.primary - } - }, - - vertical: { - '& $indicator': { - width: 4 - }, - - '& .MuiTab-root': { - padding: spacing(0, 2), - - '& .MuiTab-wrapper': { - alignItems: 'flex-start' - } - } - } - }, - - // Tab - MuiTab: { - root: { - padding: spacing(0, 1), - marginRight: spacing(3), - minWidth: '56px!important', - '&[class*="MuiTab-labelIcon"] .MuiTab-wrapper': { - flexFlow: 'row', - alignItems: 'center' - }, - '&[class*="MuiTab-labelIcon"] .MuiTab-wrapper > .MuiSvgIcon-root': { - marginRight: spacing(1), - marginBottom: 0 - } - }, - textColorPrimary: { - color: variables.palette.primary.main, - opacity: 1, - '&$selected': { - color: variables.palette.text.primary - }, - '&$disabled': { - color: variables.palette.action.disabled - } - } - }, - - MuiDivider: { - root: { - backgroundColor: variables.palette.other.divider - }, - light: { - backgroundColor: colors.shades.light[12] - } - }, - - // Switch - MuiSwitch: { - root: { - height: spacing(4.5), - width: spacing(6), - padding: spacing(1), - overflow: 'visible', - - '& + .MuiFormControlLabel-label': { - ...variables.typography.body2, - marginLeft: spacing(0.25), - color: variables.palette.text.primary - } - }, - - switchBase: { - padding: spacing(1.5), - borderRadius: '50%', - transform: 'translate(1px, 1px)', - color: variables.palette.text.secondary, - - '&$checked': { - '& input': { - left: spacing(-1.5) - }, - - transform: 'translate(13px, 1px)', - color: variables.palette.common.white, - - '& + $track': { - opacity: 1 - } - } - }, - - thumb: { - width: spacing(1.25), - height: spacing(1.25), - boxShadow: 'none' - }, - - input: { - width: spacing(6), - left: 0 - }, - - track: { - height: 'auto', - border: `2px solid ${variables.palette.text.secondary}`, - borderRadius: spacing(2), - opacity: 1, - backgroundColor: variables.palette.common.white - }, - - colorPrimary: { - '&$checked': { - color: variables.palette.common.white, - - '& + $track': { - backgroundColor: variables.palette.primary.main, - borderColor: 'transparent' - }, - - '&$disabled': { - color: variables.palette.grey[100], - - '& + $track': { - backgroundColor: variables.palette.text.disabled - } - } - }, - - '&$disabled': { - color: variables.palette.text.disabled, - - '& + $track': { - opacity: 1, - backgroundColor: variables.palette.common.white, - borderColor: variables.palette.text.disabled - } - } - }, - - colorSecondary: { - '&$checked': { - color: variables.palette.common.white, - - '& + $track': { - backgroundColor: variables.palette.secondary.main, - borderColor: 'transparent' - }, - - '&$disabled': { - color: variables.palette.grey[100], - - '& + $track': { - backgroundColor: variables.palette.text.disabled - } - } - }, - - '&$disabled': { - color: variables.palette.text.disabled, - - '& + $track': { - opacity: 1, - backgroundColor: variables.palette.common.white, - borderColor: variables.palette.text.disabled - } - } - }, - - sizeSmall: { - height: spacing(4.5), - width: spacing(6), - padding: spacing(1), - - '& $switchBase': { - padding: spacing(1.5), - transform: 'translate(0, 1px)', - - '&$checked': { - transform: 'translate(15px, 1px)' - } - }, - '& $thumb': { - width: spacing(1.25), - height: spacing(1.25) - } - } - }, - - // Breadcrumbs - MuiBreadcrumbs: { - li: { - '& .MuiTypography-root': { - ...variables.typography.body2, - display: 'flex', - flexDirection: 'row', - alignItems: 'center' - }, - '& .MuiSvgIcon-root': { - fontSize: `${variables.typography.body2.lineHeight}em`, - marginRight: spacing(1) - } - }, - - separator: { - marginLeft: spacing(0.5), - marginRight: spacing(0.5) - } - }, - - // Lists - MuiList: { - root: { - // Indent sublevels, ugly but needed to avoid issues with hover - '& .MuiList-root': { - '& .MuiListItem-root': { - paddingLeft: spacing(4) - }, - - '& .MuiList-root': { - '& .MuiListItem-root': { - paddingLeft: spacing(6) - }, - - '& .MuiList-root': { - '& .MuiListItem-root': { - paddingLeft: spacing(8) - }, - - '& .MuiList-root': { - '& .MuiListItem-root': { - paddingLeft: spacing(10) - } - } - } - } - } - } - }, - - MuiListItemIcon: { - root: { - minWidth: spacing(5.75), - marginLeft: spacing(0.75), - - '& .MuiSvgIcon-root': { - fontSize: spacing(3) - } - } - }, - - MuiListItemAvatar: { - root: { - '& .MuiAvatar-root': { - height: spacing(4.5), - width: spacing(4.5) - }, - '& .MuiSvgIcon-root': { - fontSize: spacing(2.5) - } - } - }, - - // Tooltip - MuiTooltip: { - tooltip: { - ...variables.typography.caption, - backgroundColor: variables.palette.other.tooltip - }, - - arrow: { - color: variables.palette.other.tooltip - } - }, - - // Dialog - MuiDialogTitle: { - root: { - padding: spacing(3, 3, 2) - } - }, - - MuiDialogContent: { - root: { - '& .MuiFormGroup-root': { - padding: spacing(1, 0) - } - } - }, - - // Slider - MuiSlider: { - root: {} - }, - - // MuiToggleButtonGroup - MuiToggleButtonGroup: { - groupedHorizontal: { - '&:not(:last-child)': { - marginRight: spacing(0.25), - borderTopRightRadius: spacing(0.5), - borderBottomRightRadius: spacing(0.5) - }, - '&:not(:first-child)': { - marginLeft: 0, - borderLeft: 'none', - borderTopLeftRadius: spacing(0.5), - borderBottomLeftRadius: spacing(0.5) - } - }, - groupedVertical: { - '&:not(:last-child)': { - marginBottom: spacing(0.25), - borderBottomLeftRadius: spacing(0.5), - borderBottomRightRadius: spacing(0.5) - }, - '&:not(:first-child)': { - borderTopLeftRadius: spacing(0.5), - borderTopRightRadius: spacing(0.5) - } - } - }, - - MuiTablePagination: { - select: { - paddingRight: spacing(7.5), - paddingLeft: spacing(1.5) - }, - input: { - height: spacing(4), - border: `2px solid ${variables.palette.other.divider}`, - borderRadius: spacing(0.5), - fontWeight: variables.typography.fontWeightMedium, - '& .MuiSelect-icon': { - top: '50%', - transform: 'translateY(-50%)', - width: spacing(2.25), - height: spacing(2.25), - right: spacing(0.75) - } - }, - caption: { - ...variables.typography.caption, - '&:first-of-type': { - color: variables.palette.text.secondary - } - }, - toolbar: { - minHeight: 0, - marginTop: spacing(1) - }, - actions: { - '& button:last-child': { - marginLeft: spacing(2) - } - } - }, - - MuiTableCell: { - head: { - ...variables.typography.caption, - color: variables.palette.text.secondary - }, - stickyHeader: { - backgroundColor: variables.palette.common.white - } - }, - - // MuiToggleButton - MuiToggleButton: { - root: { - width: spacing(4.5), - height: spacing(4.5), - border: 'none', - borderRadius: spacing(0.5), - color: variables.palette.grey[500], - '&$selected': { - color: variables.palette.primary.main, - backgroundColor: variables.palette.primary.relatedLight, - '&:hover': { - backgroundColor: variables.palette.primary.relatedLight - } - } - }, - sizeSmall: { - width: spacing(3), - height: spacing(3), - '& .MuiSvgIcon-root': { - maxWidth: spacing(2.5), - maxHeight: spacing(2.5) - } - }, - sizeLarge: { - width: spacing(7), - height: spacing(7) - } - }, - - MuiChip: { - root: { - backgroundColor: variables.palette.grey[100], - '&:hover': { - backgroundColor: variables.palette.grey[200] - }, - '& .MuiAvatar-root': { - backgroundColor: '#7f3c8d', - color: variables.palette.common.white - } - }, - colorPrimary: { - '&$disabled': { - backgroundColor: variables.palette.grey[100], - color: variables.palette.text.primary - }, - '&:hover': { - backgroundColor: variables.palette.primary.dark - } - }, - colorSecondary: { - '&$disabled': { - backgroundColor: variables.palette.grey[100] - }, - '&:hover': { - backgroundColor: variables.palette.secondary.light - } - }, - label: { - fontFamily: '"Open Sans", sans-serif', - letterSpacing: 0.25 - }, - labelSmall: { - fontSize: variables.typography.caption.fontSize, - fontWeight: variables.typography.fontWeightLight - }, - outlined: { - transition: `border-color 250ms cubic-bezier(0.4, 0, 0.2, 1), color 250ms cubic-bezier(0.4, 0, 0.2, 1)`, - '&$disabled': { - backgroundColor: 'transparent' - }, - '&:hover': { - backgroundColor: 'transparent', - borderColor: variables.palette.grey[200], - '&$clickable': { - backgroundColor: 'transparent' - } - } - }, - outlinedPrimary: { - '&:hover': { - backgroundColor: 'transparent', - borderColor: variables.palette.primary.dark, - color: variables.palette.primary.dark, - '&$clickable': { - backgroundColor: 'transparent' - } - } - }, - outlinedSecondary: { - '&:hover': { - backgroundColor: 'transparent', - borderColor: variables.palette.secondary.dark, - color: variables.palette.secondary.dark, - '&$clickable': { - backgroundColor: 'transparent' - } - } - }, - clickable: { - '&:focus': { - webkitTapHighlightColor: 'none' - } - } - } - }, - // Props - props: { - MuiButtonBase: { - disableRipple: true - }, - MuiButton: { - disableElevation: true - }, - MuiTextField: { - variant: 'outlined' - }, - MuiSelect: { - variant: 'outlined', - MenuProps: { - getContentAnchorEl: null, - anchorOrigin: { - vertical: 'bottom', - horizontal: 'left' - } - } - }, - MuiOutlinedInput: { - notched: false - }, - MuiCheckbox: { - size: 'small', - color: 'primary' - }, - MuiRadio: { - size: 'small', - color: 'primary' - }, - MuiSwitch: { - color: 'primary' - }, - MuiInputAdornment: { - disableTypography: true - }, - MuiListItemText: { - primaryTypographyProps: { - variant: 'body2', - style: { fontWeight: variables.typography.fontWeightBold }, - noWrap: true - }, - secondaryTypographyProps: { variant: 'caption' } - }, - MuiSkeleton: { - animation: 'wave' - }, - MuiTabs: { - indicatorColor: 'primary', - textColor: 'primary', - TabIndicatorProps: { - classes: { - colorPrimary: 'colorPrimary' - } + // Styles and props overrides for components + components: { + MuiCssBaseline: { + styleOverrides: { + ...CssBaseline } }, - MuiTypography: { - color: 'textPrimary' - }, - MuiDialogContentText: { - variant: 'body2' - }, - MuiToggleButtonGroup: { - orientation: 'horizontal', - exclusive: true - }, - CircularProgress: { - size: 40, - thickness: 4 - }, - MuiSlider: { - color: 'primary', - marks: false - }, - MuiDialog: { - maxWidth: 'md' - } + ...buttonsOverrides, + ...formsOverrides, + ...navigationOverrides, + ...dataDisplayOverrides, + ...feedbackOverrides, + ...surfacesOverrides } }; -export function createTheme(options = {}) { - const themeOptions = { - ...cartoThemeOptions, - ...options, - components: { - MuiToggleButton: { - root: { - border: 'none' - } - } - } - }; - - let theme = createMuiTheme(themeOptions); - - theme = responsiveFontSizes(theme, { - breakpoints: themeOptions.breakpoints.keys, - disableAlign: false, - factor: 2, - variants: [ - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'subtitle1', - 'subtitle2', - 'body1', - 'body2', - 'button', - 'caption', - 'overline' - ] - }); - - return theme; -} +// @ts-ignore +export const theme = responsiveFontSizes(createTheme(cartoThemeOptions)); diff --git a/packages/react-ui/src/theme/sections/components/buttons.js b/packages/react-ui/src/theme/sections/components/buttons.js new file mode 100644 index 000000000..d894e8082 --- /dev/null +++ b/packages/react-ui/src/theme/sections/components/buttons.js @@ -0,0 +1,516 @@ +import { getSpacing } from '../../themeUtils'; +import { commonPalette } from '../palette'; +import { themeTypography } from '../typography'; +import { themeShadows } from '../shadows'; +import { ICON_SIZE, ICON_SIZE_M } from '../../themeConstants'; + +const sizeSmall = getSpacing(3); +const sizeMedium = getSpacing(4); +const sizeLarge = getSpacing(6); +const radius = getSpacing(0.5); + +export const buttonsOverrides = { + // Button Base + MuiButtonBase: { + defaultProps: { + disableRipple: true + }, + + styleOverrides: { + root: { + '& .MuiSvgIcon-root, & svg': { + display: 'flex', + fontSize: ICON_SIZE, + width: ICON_SIZE, + minWidth: ICON_SIZE, + height: ICON_SIZE + } + } + } + }, + + // Button + MuiButton: { + defaultProps: { + disableElevation: true + }, + + styleOverrides: { + root: ({ ownerState }) => ({ + // maxWidth: '192px', TODO temporary disabled waiting for a design definition + + '&:hover, &:focus-visible': { + boxShadow: themeShadows[0], + + ...(ownerState.variant !== 'contained' && { + ...(ownerState.color === 'primary' && { + backgroundColor: commonPalette.primary.background + }), + ...(ownerState.color === 'secondary' && { + backgroundColor: commonPalette.secondary.background + }), + + ...(ownerState.color === 'error' && { + background: commonPalette.error.relatedLight + }) + }), + ...(ownerState.variant === 'contained' && + ownerState.color === 'secondary' && { + backgroundColor: commonPalette.secondary.light + }) + }, + '& svg:not(.doNotFillIcon) path': { + fill: 'currentColor' + }, + // Pairing buttons separation + '& + &': { + marginLeft: getSpacing(1) + } + }), + + contained: { + boxShadow: 'none', + + '&.Mui-disabled': { + color: commonPalette.text.disabled, + backgroundColor: commonPalette.action.disabledBackground + } + }, + outlined: { + '&.Mui-disabled': { + color: commonPalette.text.disabled, + borderColor: commonPalette.default.outlinedBorder + } + }, + outlinedPrimary: { + borderColor: commonPalette.primary.main + }, + outlinedSecondary: { + borderColor: commonPalette.secondary.main + }, + outlinedError: { + borderColor: commonPalette.error.main + }, + containedPrimary: { + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.primary.dark + } + }, + containedError: { + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.error.dark + } + }, + + startIcon: { + marginRight: getSpacing(0.75), + + '& .MuiSvgIcon-root, & svg': { + fontSize: ICON_SIZE, + width: ICON_SIZE, + minWidth: ICON_SIZE, + height: ICON_SIZE + }, + '&.MuiButton-iconSizeSmall': { + marginRight: getSpacing(0.5), + marginLeft: getSpacing(-0.5) + } + }, + endIcon: { + marginLeft: getSpacing(0.75), + + '& .MuiSvgIcon-root, & svg': { + fontSize: ICON_SIZE, + width: ICON_SIZE, + minWidth: ICON_SIZE, + height: ICON_SIZE + }, + '&.MuiButton-iconSizeSmall': { + marginLeft: getSpacing(0.5), + marginRight: getSpacing(-0.5) + } + }, + + sizeSmall: { + height: sizeSmall, + padding: getSpacing(0, 1.5), + ...themeTypography.caption, + lineHeight: sizeSmall, + fontWeight: 500, + letterSpacing: '0.4px' + }, + sizeMedium: { + height: sizeMedium, + padding: getSpacing(0, 2), + lineHeight: sizeMedium + }, + sizeLarge: { + height: sizeLarge, + padding: getSpacing(0, 2.5), + ...themeTypography.body1, + lineHeight: sizeLarge, + fontWeight: 500, + letterSpacing: '0.25px' + } + }, + + variants: [ + // Custom color and its variants + { + props: { variant: 'contained', color: 'default' }, + style: { + color: commonPalette.text.primary, + backgroundColor: commonPalette.default.main, + borderColor: commonPalette.text.primary, + + '&.Mui-disabled': { + color: commonPalette.text.disabled, + backgroundColor: commonPalette.action.disabledBackground + }, + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.default.dark + } + } + }, + { + props: { variant: 'outlined', color: 'default' }, + style: { + color: commonPalette.text.primary, + borderColor: commonPalette.text.primary, + + '&.Mui-disabled': { + color: commonPalette.text.disabled, + borderColor: commonPalette.default.outlinedBorder + }, + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.action.hover, + borderColor: commonPalette.text.primary + } + } + }, + { + props: { variant: 'text', color: 'default' }, + style: { + color: commonPalette.text.primary, + + '&.Mui-disabled': { + color: commonPalette.text.disabled + }, + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.action.hover + } + } + } + ] + }, + + // Mui Button Group + MuiButtonGroup: { + defaultProps: { + disableRipple: true, + disableElevation: true + }, + + styleOverrides: { + root: ({ ownerState }) => ({ + '& .MuiButton-root + .MuiButton-root': { + marginLeft: 0 + }, + + ...(ownerState.variant === 'text' && { + boxShadow: themeShadows[1], + borderColor: commonPalette.default.dark, + + '& .MuiButtonGroup-grouped:not(:last-of-type)': { + borderColor: commonPalette.default.dark + } + }), + ...(ownerState.variant === 'outlined' && { + ...(ownerState.color === 'default' && { + '& .MuiButtonBase-root.Mui-disabled': { + borderColor: commonPalette.text.primary + } + }), + ...(ownerState.color === 'primary' && { + '& .MuiButtonBase-root.Mui-disabled': { + borderColor: commonPalette.primary.main + } + }), + ...(ownerState.color === 'secondary' && { + '& .MuiButtonBase-root.Mui-disabled': { + borderColor: commonPalette.secondary.main + } + }), + ...(ownerState.orientation !== 'vertical' && { + '& .MuiButtonGroup-grouped:not(:last-of-type):hover, & .Mui-disabled:not(:last-of-type)': + { + borderRightColor: 'transparent' + } + }) + }), + ...(ownerState.variant === 'contained' && { + ...(ownerState.color === 'default' && { + '& .MuiButtonGroup-grouped:not(:last-of-type)': { + borderRightColor: commonPalette.default.dark, + + '&.Mui-disabled': { + ...(ownerState.color === 'default' && { + borderColor: commonPalette.default.dark + }), + ...(ownerState.color === 'primary' && { + borderColor: commonPalette.primary.dark + }), + ...(ownerState.color === 'secondary' && { + borderColor: commonPalette.secondary.dark + }) + } + } + }) + }) + }) + } + }, + + // Icon Button + MuiIconButton: { + styleOverrides: { + root: ({ ownerState }) => ({ + borderRadius: getSpacing(0.5), + + ...(ownerState.color === 'default' && { + color: commonPalette.text.secondary + }), + '& .MuiSvgIcon-root, & svg': { + fontSize: ICON_SIZE, + width: ICON_SIZE, + minWidth: ICON_SIZE, + height: ICON_SIZE + }, + '& svg:not(.doNotFillIcon) path': { + fill: 'currentColor' + }, + '&:hover, &:focus-visible': { + ...(ownerState.color === 'default' && { + backgroundColor: commonPalette.action.hover + }), + ...(ownerState.color === 'primary' && { + backgroundColor: commonPalette.primary.background + }), + ...(ownerState.color === 'secondary' && { + backgroundColor: commonPalette.secondary.background + }) + } + }), + + sizeSmall: { + width: sizeSmall, + height: sizeSmall + }, + sizeMedium: { + width: sizeMedium, + height: sizeMedium + }, + sizeLarge: { + width: sizeLarge, + height: sizeLarge + } + } + }, + + // MuiToggleButton + MuiToggleButton: { + styleOverrides: { + root: { + minWidth: sizeMedium, + height: sizeMedium, + padding: getSpacing(0, 1), + color: commonPalette.text.secondary, + border: 'none', + borderRadius: radius, + transition: 'background-color 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + + '&:hover': { + backgroundColor: commonPalette.action.hover + }, + // Pairing buttons separation + '& + &': { + marginLeft: getSpacing(0.5) + }, + '.MuiSvgIcon-root, & svg': { + margin: getSpacing(0, -0.75) + }, + '&.Mui-selected': { + color: commonPalette.primary.main, + backgroundColor: commonPalette.primary.background, + + '&:hover': { + backgroundColor: commonPalette.action.hover + } + }, + '&.Mui-disabled': { + border: 'none' + } + }, + sizeLarge: { + minWidth: sizeLarge, + height: sizeLarge, + ...themeTypography.body1 + }, + sizeSmall: { + minWidth: sizeSmall, + height: sizeSmall, + ...themeTypography.caption, + fontWeight: 500 + } + } + }, + + // MuiToggleButtonGroup + MuiToggleButtonGroup: { + defaultProps: { + orientation: 'horizontal', + exclusive: true + }, + + styleOverrides: { + root: { + alignItems: 'center', + justifyContent: 'center', + borderRadius: getSpacing(1), + boxShadow: themeShadows[1], + backgroundColor: commonPalette.background.paper, + + '& .MuiToggleButtonGroup-grouped:not(:first-of-type), &.Mui-Selected, & .MuiToggleButtonGroup-grouped:not(:last-of-type)': + { + borderRadius: radius + }, + '.MuiDivider-root': { + height: sizeLarge, + margin: getSpacing(0, 1), + marginLeft: getSpacing(0.5) + } + }, + // Styles applied to the children if orientation="horizontal" + groupedHorizontal: { + height: sizeMedium, + margin: getSpacing(1), + + '&:not(:last-of-type)': { + marginRight: getSpacing(0.5), + marginLeft: 0, + borderLeft: 'none' + }, + '&:first-of-type': { + marginLeft: getSpacing(1) + }, + '&.MuiToggleButton-sizeSmall': { + height: sizeSmall, + margin: getSpacing(0.5), + + '&:not(:first-of-type)': { + marginLeft: 0 + }, + '& + .MuiDivider-root': { + height: sizeMedium + } + } + }, + // Styles applied to the children if orientation="vertical" + groupedVertical: { + width: sizeMedium, + margin: getSpacing(1), + + '&.MuiToggleButton-root': { + marginLeft: getSpacing(1), + marginBottom: getSpacing(0.5) + }, + '&.MuiToggleButton-sizeSmall': { + width: sizeSmall, + margin: getSpacing(0.5), + + '&:not(:first-of-type)': { + marginTop: 0 + } + } + } + } + }, + + // FAB button + MuiFab: { + defaultProps: { + color: 'primary' + }, + + styleOverrides: { + root: ({ ownerState }) => ({ + '&:focus': { + boxShadow: themeShadows[6] + }, + + '& .MuiSvgIcon-root, & svg': { + fontSize: ICON_SIZE_M, + width: ICON_SIZE_M, + minWidth: ICON_SIZE_M, + height: ICON_SIZE_M + }, + '&.MuiFab-extended': { + ...themeTypography.body1, + fontWeight: 500, + width: 'auto', + height: getSpacing(7), + paddingRight: getSpacing(3), + borderRadius: getSpacing(8), + + '& .MuiSvgIcon-root': { + marginRight: getSpacing(1.5) + } + }, + + ...(ownerState.color === 'default' && { + color: commonPalette.text.primary, + backgroundColor: commonPalette.background.paper, + + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.default.light + } + }) + }), + + sizeSmall: { + width: getSpacing(4), + height: getSpacing(4), + minHeight: getSpacing(4), + + '& .MuiSvgIcon-root, & svg': { + fontSize: ICON_SIZE, + width: ICON_SIZE, + minWidth: ICON_SIZE, + height: ICON_SIZE + }, + '&.MuiFab-extended': { + ...themeTypography.caption, + width: 'auto', + height: getSpacing(4), + paddingRight: getSpacing(2), + + '& .MuiSvgIcon-root': { + marginRight: getSpacing(1) + } + } + }, + sizeMedium: { + '&.MuiFab-extended': { + ...themeTypography.button, + height: getSpacing(6) + } + }, + + secondary: { + '&:hover': { + backgroundColor: commonPalette.secondary.light + } + } + } + } +}; diff --git a/packages/react-ui/src/theme/sections/components/dataDisplay.js b/packages/react-ui/src/theme/sections/components/dataDisplay.js new file mode 100644 index 000000000..f82915edc --- /dev/null +++ b/packages/react-ui/src/theme/sections/components/dataDisplay.js @@ -0,0 +1,463 @@ +import { ICON_SIZE, ICON_SIZE_M } from '../../themeConstants'; +import { getSpacing } from '../../themeUtils'; +import { commonPalette } from '../palette'; +import { themeTypography } from '../typography'; + +const tooltipArrowSize = 1; +const tooltipSeparation = 0.5; +const tooltipMargin = tooltipArrowSize + tooltipSeparation; + +export const dataDisplayOverrides = { + // Divider + MuiDivider: { + styleOverrides: { + root: { + backgroundColor: 'transparent', + borderColor: commonPalette.divider + }, + light: { + borderColor: commonPalette.white[12] + } + } + }, + + // List + MuiList: { + styleOverrides: { + root: { + // Indent sublevels, ugly but needed to avoid issues with hover + '& .MuiList-root': { + '& .MuiListItem-root': { + paddingLeft: getSpacing(4) + }, + + '& .MuiList-root': { + '& .MuiListItem-root': { + paddingLeft: getSpacing(6) + }, + + '& .MuiList-root': { + '& .MuiListItem-root': { + paddingLeft: getSpacing(8) + }, + + '& .MuiList-root': { + '& .MuiListItem-root': { + paddingLeft: getSpacing(10) + } + } + } + } + } + } + } + }, + + // List Item + MuiListItemText: { + defaultProps: { + primaryTypographyProps: { + variant: 'body2', + noWrap: true + }, + secondaryTypographyProps: { variant: 'caption' } + } + }, + MuiListItemIcon: { + styleOverrides: { + root: { + marginRight: getSpacing(1), + + '& .MuiSvgIcon-root': { + fontSize: ICON_SIZE_M + }, + '.MuiMenuItem-root.MuiButtonBase-root &': { + minWidth: getSpacing(2.25) + } + } + } + }, + MuiListItemAvatar: { + styleOverrides: { + root: { + '& .MuiAvatar-root': { + height: getSpacing(4), + width: getSpacing(4) + }, + '& .MuiSvgIcon-root': { + fontSize: ICON_SIZE + } + } + } + }, + + // Tooltip + MuiTooltip: { + defaultProps: { + arrow: true, + placement: 'top', + enterDelay: 1000, + leaveDelay: 200 + }, + + styleOverrides: { + tooltip: { + ...themeTypography.caption, + fontWeight: 500, + maxWidth: '240px', + backgroundColor: commonPalette.black[90], + + '.MuiTooltip-popper[data-popper-placement*="top"] &': { + marginBottom: getSpacing(tooltipSeparation), + + '&.MuiTooltip-tooltipArrow': { + marginBottom: getSpacing(tooltipMargin) + } + }, + '.MuiTooltip-popper[data-popper-placement*="right"] &': { + marginLeft: getSpacing(tooltipSeparation), + + '&.MuiTooltip-tooltipArrow': { + marginLeft: getSpacing(tooltipMargin) + } + }, + '.MuiTooltip-popper[data-popper-placement*="bottom"] &': { + marginTop: getSpacing(tooltipSeparation), + + '&.MuiTooltip-tooltipArrow': { + marginTop: getSpacing(tooltipMargin) + } + }, + '.MuiTooltip-popper[data-popper-placement*="left"] &': { + marginRight: getSpacing(tooltipSeparation), + + '&.MuiTooltip-tooltipArrow': { + marginRight: getSpacing(tooltipMargin) + } + } + }, + + arrow: { + height: getSpacing(tooltipArrowSize), + color: commonPalette.black[90] + } + } + }, + + // Popper + MuiPopper: { + styleOverrides: { + root: { + '& .MuiPaper-root': { + marginTop: getSpacing(0.5) + } + } + } + }, + + // Popover + MuiPopover: { + styleOverrides: { + root: { + '& .MuiPaper-root': { + marginTop: getSpacing(0.5) + } + } + } + }, + + // Dialog + MuiDialog: { + defaultProps: { + maxWidth: 'md' + } + }, + MuiDialogTitle: { + defaultProps: { + variant: 'subtitle1' + }, + + styleOverrides: { + root: { + padding: getSpacing(3, 3, 2) + } + } + }, + MuiDialogContent: { + styleOverrides: { + root: { + '& .MuiFormGroup-root': { + padding: getSpacing(1, 0) + } + } + } + }, + MuiDialogContentText: { + defaultProps: { + variant: 'body2' + }, + + styleOverrides: { + root: { + color: commonPalette.text.primary + } + } + }, + + // Table + MuiTablePagination: { + styleOverrides: { + select: { + paddingRight: getSpacing(7.5), + paddingLeft: getSpacing(1.5) + }, + input: { + height: getSpacing(4), + width: 'auto', + border: `2px solid ${commonPalette.divider}`, + borderRadius: getSpacing(0.5), + fontWeight: themeTypography.fontWeightMedium, + '& .MuiSelect-icon': { + top: '50%', + transform: 'translateY(-50%)', + width: getSpacing(2.25), + height: getSpacing(2.25), + right: getSpacing(0.75) + } + }, + caption: { + ...themeTypography.caption, + '&:first-of-type': { + color: commonPalette.text.secondary + } + }, + toolbar: { + minHeight: 0, + marginTop: getSpacing(1) + }, + actions: { + '& button:last-child': { + marginLeft: getSpacing(2) + } + } + } + }, + MuiTableCell: { + styleOverrides: { + head: { + ...themeTypography.caption, + color: commonPalette.text.secondary + }, + stickyHeader: { + backgroundColor: commonPalette.common.white + } + } + }, + + // Chip + MuiChip: { + defaultProps: { + color: 'primary' + }, + + styleOverrides: { + root: { + maxWidth: '192px', + padding: getSpacing(0, 0.5), + + '& .MuiAvatar-root': { + width: ICON_SIZE_M, + height: ICON_SIZE_M, + margin: 0, + color: commonPalette.secondary.contrastText, + backgroundColor: commonPalette.background.paper + }, + '& .MuiChip-icon': { + margin: 0, + marginLeft: getSpacing(0.5) + }, + '& img': { + width: ICON_SIZE_M, + height: ICON_SIZE_M + }, + '&.Mui-disabled': { + color: commonPalette.text.disabled, + backgroundColor: commonPalette.action.disabledBackground, + + '& .MuiChip-deleteIcon, & .MuiChip-icon': { + color: commonPalette.action.disabled + } + } + }, + + // Variants + filled: { + border: 0, + backgroundColor: commonPalette.default.main, + + '& .MuiChip-iconColorPrimary': { + color: commonPalette.primary.contrastText + } + }, + filledPrimary: { + backgroundColor: commonPalette.primary.main + }, + filledSecondary: { + backgroundColor: commonPalette.secondary.main + }, + outlined: { + borderColor: commonPalette.default.outlinedBorder, + + '&.Mui-disabled': { + borderColor: commonPalette.default.outlinedBorder, + backgroundColor: 'transparent' + } + }, + outlinedPrimary: { + borderColor: commonPalette.primary.main + }, + outlinedSecondary: { + borderColor: commonPalette.secondary.main + }, + + // Sizes + sizeSmall: { + '& img': { + width: ICON_SIZE, + height: ICON_SIZE + }, + '& .MuiAvatar-root': { + width: ICON_SIZE, + height: ICON_SIZE + }, + '& .MuiChip-icon': { + marginLeft: getSpacing(0.25) + } + }, + + // Inner elements + label: { + ...themeTypography.button, + padding: getSpacing(0, 0.75) + }, + labelSmall: { + ...themeTypography.caption, + fontWeight: 500, + padding: getSpacing(0, 0.5) + }, + deleteIcon: { + width: ICON_SIZE, + height: ICON_SIZE, + margin: 0, + marginLeft: '2px', // Forced to a non-standard value to meet with design + marginRight: '3px', // Forced to a non-standard value to meet with design + transition: 'color 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + + '&.MuiChip-deleteIconColorDefault': { + color: commonPalette.text.secondary, + + '&:hover': { + color: commonPalette.text.primary + } + } + }, + deleteIconSmall: { + width: getSpacing(2), + height: getSpacing(2), + marginRight: 0 + }, + + clickable: { + '&:active': { + boxShadow: 'none' + }, + '&:hover': { + '& .MuiChip-deleteIconColorDefault': { + color: commonPalette.text.primary + } + }, + + '&.MuiChip-outlined': { + transitionProperty: 'background, color, border-color', + transitionDuration: '300ms', + transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', + + '&:hover': { + backgroundColor: 'transparent', + + '&.MuiChip-colorPrimary': { + color: commonPalette.primary.dark, + borderColor: commonPalette.primary.dark + }, + '&.MuiChip-colorSecondary': { + color: commonPalette.secondary.dark, + borderColor: commonPalette.secondary.dark + }, + '&.MuiChip-colorDefault': { + borderColor: commonPalette.default.dark + } + } + }, + '&.MuiChip-filled': { + '&:hover': { + '&.MuiChip-colorSecondary': { + backgroundColor: commonPalette.secondary.light + }, + '&.MuiChip-colorDefault': { + backgroundColor: commonPalette.default.dark + } + } + } + } + } + }, + + // Avatar + MuiAvatar: { + styleOverrides: { + root: { + color: commonPalette.secondary.contrastText, + backgroundColor: commonPalette.secondary.main + }, + img: { + // border: `1px solid ${commonPalette.default.outlinedBorder}` TODO fix the background color overlap + }, + circular: { + '& img': { + borderRadius: '50%' + } + }, + rounded: { + '& img': { + borderRadius: getSpacing(0.5) + } + } + } + }, + + // Skeleton + MuiSkeleton: { + defaultProps: { + animation: 'wave' + } + }, + + // Typography + MuiTypography: { + defaultProps: { + color: 'textPrimary' + } + }, + + // Svg Icons + MuiSvgIcon: { + styleOverrides: { + root: { + fontSize: ICON_SIZE + }, + sizeLarge: { + fontSize: ICON_SIZE_M + } + } + } +}; diff --git a/packages/react-ui/src/theme/sections/components/feedback.js b/packages/react-ui/src/theme/sections/components/feedback.js new file mode 100644 index 000000000..10665a608 --- /dev/null +++ b/packages/react-ui/src/theme/sections/components/feedback.js @@ -0,0 +1,11 @@ +export const feedbackOverrides = { + // SnackBar + MuiSnackbar: { + defaultProps: { + anchorOrigin: { + vertical: 'bottom', + horizontal: 'center' + } + } + } +}; diff --git a/packages/react-ui/src/theme/sections/components/forms.js b/packages/react-ui/src/theme/sections/components/forms.js new file mode 100644 index 000000000..324bfa73e --- /dev/null +++ b/packages/react-ui/src/theme/sections/components/forms.js @@ -0,0 +1,655 @@ +import React from 'react'; +import { ICON_SIZE, ICON_SIZE_M } from '../../themeConstants'; +import { getSpacing } from '../../themeUtils'; +import { commonPalette } from '../palette'; +import { themeShadows } from '../shadows'; +import { themeTypography } from '../typography'; +import ArrowDropIcon from '../../../assets/ArrowDropIcon'; + +const switchSizeS = 2; +const switchSizeM = 3; +const switchSizeL = 4; + +const checkboxRadioOverrides = { + root: ({ ownerState }) => ({ + padding: getSpacing(0.5), + + ...(ownerState.size === 'small' && { + padding: '3px' // Forced to a non-standard value to meet with design + }), + + '&:hover, &:focus-visible': { + backgroundColor: commonPalette.primary.background + }, + '& + .MuiFormControlLabel-label': { + ...themeTypography.body2, + marginLeft: getSpacing(0.25), + + ...(ownerState.size === 'small' && { + marginLeft: getSpacing(0.5) + }) + }, + + '& .MuiSvgIcon-root': { + fontSize: ICON_SIZE_M, + + ...(ownerState.size === 'small' && { + fontSize: ICON_SIZE + }) + } + }) +}; + +export const formsOverrides = { + // Checkbox + MuiCheckbox: { + styleOverrides: { + ...checkboxRadioOverrides + } + }, + + // Radio Button + MuiRadio: { + styleOverrides: { + ...checkboxRadioOverrides + } + }, + + // Text Field + MuiTextField: { + defaultProps: { + fullWidth: true, + size: 'small', + + InputLabelProps: { + shrink: true + }, + SelectProps: { + IconComponent: ArrowDropIcon, + size: 'small' + } + }, + styleOverrides: { + root: ({ ownerState }) => ({ + '& legend': { + display: 'none' + }, + + // Select bool + ...(ownerState.select === true && { + '& .MuiInputBase-root': { + padding: 0, + + '&.MuiOutlinedInput-root, &.MuiFilledInput-root': { + padding: 0 + }, + '& .MuiInputAdornment-positionEnd': { + marginRight: getSpacing(3) + }, + '& .MuiSelect-select': { + padding: getSpacing(1.5), + + '&.MuiInputBase-input': { + paddingLeft: getSpacing(2), + paddingRight: getSpacing(5), + + '&.MuiSelect-standard': { + paddingLeft: 0 + } + }, + '&:focus': { + background: 'transparent' + }, + '& .MuiTypography-root': { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' + } + }, + '& .MuiSelect-icon': { + right: getSpacing(2), + color: commonPalette.text.secondary + }, + '& .MuiSelect-iconStandard': { + right: 0 + } + }, + + '& .MuiInputBase-sizeSmall': { + '&.MuiInputBase-root.MuiFilledInput-root': { + padding: 0 + }, + '& .MuiSelect-select': { + ...themeTypography.body2, + padding: getSpacing(0.75), + + '&.MuiInputBase-input': { + paddingLeft: getSpacing(1.5), + paddingRight: getSpacing(4) + } + }, + '&.MuiOutlinedInput-root.MuiInputBase-sizeSmall': { + padding: 0 + }, + '& .MuiSelect-icon': { + right: getSpacing(1.5) + } + } + }) + }) + } + }, + + // Input Base + MuiInputBase: { + styleOverrides: { + root: { + height: getSpacing(6), + padding: getSpacing(0, 2), + ...themeTypography.body1, + + '& input': { + padding: 0, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + + '&::placeholder': { + opacity: 1, + color: commonPalette.text.hint + } + }, + + '&.MuiInputBase-formControl::after': { + top: 0, + transform: 'none', + opacity: 0 + }, + '&.MuiInputBase-formControl.Mui-focused::after': { + transform: 'none', + opacity: 1 + }, + '& legend': { display: 'none' }, + + // Variants + '&.MuiFilledInput-root': { + padding: getSpacing(0, 2), + borderRadius: getSpacing(0.5), + backgroundColor: commonPalette.default.background, + + '&:hover': { + backgroundColor: commonPalette.default.background + }, + '&::before': { + top: 0, + borderRadius: getSpacing(0.5), + border: '1px solid transparent', + transition: 'border 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms' + }, + '&:hover:not(.Mui-disabled)::before': { + borderColor: commonPalette.text.primary + }, + '&::after': { + borderRadius: getSpacing(0.5), + border: '1px solid transparent' + }, + '&.MuiInputBase-sizeSmall': { + padding: getSpacing(0, 1.5) + }, + '&.Mui-focused': { + backgroundColor: commonPalette.background.paper, + + '&::after': { + border: `2px solid ${commonPalette.primary.main}` + } + }, + '&.Mui-disabled': { + backgroundColor: commonPalette.default.background, + + '&::before': { + borderBottomStyle: 'solid' + } + }, + '&.Mui-error::after': { + opacity: 1, + border: `2px solid ${commonPalette.error.light}` + } + }, + + '&.MuiOutlinedInput-root': { + padding: getSpacing(0, 2), + + '&.MuiInputBase-sizeSmall': { + padding: getSpacing(0, 1.5) + }, + '&.Mui-focused': { + backgroundColor: commonPalette.background.paper + }, + '&.Mui-disabled': { + backgroundColor: commonPalette.default.background + }, + '& .MuiOutlinedInput-notchedOutline': { + top: 0, + borderColor: commonPalette.default.outlinedBorder, + transition: 'border 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms' + }, + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + transition: 'none' + }, + '&.Mui-error .MuiOutlinedInput-notchedOutline': { + border: `2px solid ${commonPalette.error.light}` + } + }, + + '&.MuiInput-underline': { + marginTop: 0, + padding: 0, + + '&::before': { + borderColor: commonPalette.default.outlinedBorder + }, + '&:hover:not(.Mui-disabled)::before': { + borderBottom: `1px solid ${commonPalette.text.primary}` + }, + '&:not(.Mui-disabled)::after': { + borderBottom: '1px solid transparent' + }, + '&.Mui-focused::after': { + borderBottom: `2px solid ${commonPalette.primary.main}` + }, + '&.Mui-error::after': { + opacity: 1, + borderBottom: `2px solid ${commonPalette.error.light}` + }, + '&.Mui-disabled::before': { + borderBottomStyle: 'solid' + } + }, + + // TextArea (multiline) + '&.MuiInputBase-multiline': { + height: 'auto', + minHeight: getSpacing(12), + alignItems: 'flex-start', + padding: getSpacing(0, 0.25), + + '& textarea': { + padding: getSpacing(1.5, 1.75), + ...themeTypography.body1, + + '&::placeholder, &.Mui-disabled::placeholder': { + opacity: 1, + color: commonPalette.text.hint + } + }, + + '&.MuiInputBase-sizeSmall': { + minHeight: getSpacing(9), + padding: getSpacing(0, 0.25), + + '& textarea': { + padding: getSpacing(1, 1.25), + ...themeTypography.body2 + } + } + }, + + // Select Multiple selection + '&.MuiInputBase-root .MuiSelect-multiple.MuiInputBase-input': { + paddingLeft: 0, + paddingRight: getSpacing(3) + } + }, + + // size + sizeSmall: { + height: getSpacing(4), + padding: getSpacing(0, 1.5), + + '& input': { + ...themeTypography.body2 + } + } + } + }, + + // Input Adornment + MuiInputAdornment: { + styleOverrides: { + root: { + '& .MuiTypography-root': { + ...themeTypography.body1, + color: commonPalette.text.secondary + }, + '&.MuiInputAdornment-sizeSmall': { + '& .MuiTypography-root': { + ...themeTypography.body2 + } + }, + '&.MuiInputAdornment-positionStart.MuiInputAdornment-root:not(.MuiInputAdornment-hiddenLabel)': + { + marginTop: 0 + }, + '& .MuiSvgIcon-root, & svg': { + fontSize: ICON_SIZE, + width: ICON_SIZE, + minWidth: ICON_SIZE, + height: ICON_SIZE, + color: commonPalette.text.secondary + }, + '.Mui-disabled &': { + '& .MuiTypography-root, & .MuiSvgIcon-root': { + color: commonPalette.text.disabled + } + } + } + } + }, + + // Form Control + MuiFormControl: { + defaultProps: { + fullWidth: true + } + }, + + // Form Control Label (radio, checkbox and switch wrapper) + MuiFormControlLabel: { + styleOverrides: { + root: { + marginLeft: getSpacing(-0.5), + + '& .MuiSwitch-root': { + marginLeft: getSpacing(0.5) + } + } + } + }, + + // Form Helper Text + MuiFormHelperText: { + styleOverrides: { + root: { + margin: 0, + marginTop: getSpacing(1) + } + } + }, + + // Label + MuiInputLabel: { + styleOverrides: { + root: { + position: 'static', + transform: 'none', + marginBottom: getSpacing(1), + ...themeTypography.caption, + fontWeight: 500, + color: commonPalette.text.primary + }, + sizeSmall: { + marginBottom: getSpacing(0.5) + }, + standard: { + marginBottom: 0 + } + } + }, + + // Select + MuiSelect: { + defaultProps: { + IconComponent: ArrowDropIcon, + fullWidth: true, + size: 'small' + }, + + styleOverrides: { + root: { + padding: 0, + + '& .MuiSelect-icon': { + right: getSpacing(2), + color: commonPalette.text.secondary + }, + '& .MuiSelect-iconStandard': { + right: 0 + }, + '& legend': { + display: 'none' + }, + + // Variants + '&.MuiOutlinedInput-root, &.MuiFilledInput-root': { + padding: 0 + }, + '&.MuiFilledInput-root, &.MuiInput-underline': { + '&.Mui-focused::after': { + height: '100%', + transition: 'none' + } + }, + + // Size Small + '&.MuiInputBase-sizeSmall': { + ...themeTypography.body2, + + '& .MuiSelect-select': { + '&.MuiInputBase-input': { + paddingLeft: getSpacing(1.5), + paddingRight: getSpacing(4) + }, + '&.MuiSelect-standard': { + paddingLeft: 0 + } + }, + '&.MuiOutlinedInput-root.MuiInputBase-sizeSmall, &.MuiFilledInput-root.MuiInputBase-sizeSmall': + { + padding: 0 + }, + '& .MuiSelect-icon': { + right: getSpacing(1.5) + } + } + }, + select: { + padding: getSpacing(1.5), + + '&.MuiInputBase-input': { + paddingLeft: getSpacing(2), + paddingRight: getSpacing(5), + + '&.MuiSelect-standard': { + paddingLeft: 0 + } + }, + '&:focus': { + background: 'transparent' + }, + '& .MuiTypography-root': { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' + }, + + '&.MuiInputBase-inputSizeSmall': { + padding: getSpacing(0.75) + } + } + } + }, + + // Autocomplete + MuiAutocomplete: { + defaultProps: { + popupIcon: + }, + + styleOverrides: { + root: { + '& .MuiInputBase-root .MuiAutocomplete-endAdornment': { + top: getSpacing(1), + right: getSpacing(1.5) + }, + '& .MuiInputBase-sizeSmall .MuiAutocomplete-endAdornment': { + top: 0, + right: getSpacing(0.75) + }, + '& .MuiFormLabel-root': { + pointerEvents: 'auto' + } + }, + + inputRoot: { + '&[class*="MuiOutlinedInput-root"]': { + '& .MuiAutocomplete-input': { + padding: 0 + } + }, + '& .MuiAutocomplete-popupIndicator:hover, & .MuiAutocomplete-popupIndicator:focus-visible': + { + backgroundColor: 'transparent' + } + }, + + listbox: { + '& .MuiAutocomplete-option': { + minHeight: getSpacing(4), + padding: getSpacing(0, 2) + } + }, + + option: { + ...themeTypography.body2 + } + } + }, + + // Switch + MuiSwitch: { + defaultProps: { + disableRipple: true + }, + + styleOverrides: { + root: { + width: getSpacing(switchSizeM), + height: getSpacing(switchSizeS), + padding: 0, + overflow: 'visible', + + '& + .MuiTypography-root': { + marginLeft: getSpacing(1), + color: commonPalette.text.primary + }, + '& + .MuiFormControlLabel-label': { + ...themeTypography.body2 + } + }, + + switchBase: { + width: getSpacing(switchSizeL), + height: getSpacing(switchSizeL), + padding: getSpacing(0.5), + borderRadius: '50%', + color: commonPalette.text.secondary, + transform: 'translate(-8px, -8px)', + + '&:hover': { + backgroundColor: commonPalette.action.hover + }, + '&.MuiSwitch-switchBase input': { + top: getSpacing(1), + left: getSpacing(1) + }, + '&.Mui-checked': { + transform: 'translate(0, -8px)', + color: commonPalette.common.white, + + '&.MuiSwitch-switchBase input': { + left: 0 + }, + '& + .MuiSwitch-track': { + opacity: 1, + border: 0 + } + } + }, + + thumb: { + width: getSpacing(1), + height: getSpacing(1), + boxShadow: themeShadows[0], + + '.Mui-checked &': { + boxShadow: themeShadows[1] + }, + '.Mui-disabled &': { + backgroundColor: commonPalette.text.disabled + }, + '.Mui-disabled.Mui-checked &': { + backgroundColor: commonPalette.common.white + } + }, + + input: { + width: getSpacing(switchSizeM), + height: getSpacing(switchSizeS), + left: 0 + }, + + track: { + height: 'auto', + border: `1px solid ${commonPalette.text.secondary}`, + borderRadius: getSpacing(2), + opacity: 1, + backgroundColor: commonPalette.common.white, + transitionDuration: '300ms', + + '.MuiButtonBase-root.MuiSwitch-switchBase.Mui-disabled + &': { + opacity: 1, + borderColor: commonPalette.text.disabled + }, + '.MuiButtonBase-root.Mui-checked.Mui-disabled + &': { + backgroundColor: commonPalette.text.disabled + } + }, + + colorPrimary: { + '&.Mui-checked:hover': { + backgroundColor: commonPalette.primary.background + } + }, + colorSecondary: { + '&.Mui-checked:hover': { + backgroundColor: commonPalette.secondary.background + } + } + } + }, + + // Circular Progress + CircularProgress: { + defaultProps: { + size: 40, + thickness: 4 + } + }, + + // Slider + MuiSlider: { + defaultProps: { + color: 'primary', + marks: false, + size: 'small' + }, + + styleOverrides: { + thumb: { + '&:hover, &.Mui-focusVisible': { + boxShadow: `0 0 0 ${getSpacing(1)} ${commonPalette.primary.background}` + } + } + } + } +}; diff --git a/packages/react-ui/src/theme/sections/components/navigation.js b/packages/react-ui/src/theme/sections/components/navigation.js new file mode 100644 index 000000000..f37116042 --- /dev/null +++ b/packages/react-ui/src/theme/sections/components/navigation.js @@ -0,0 +1,147 @@ +import { ICON_SIZE } from '../../themeConstants'; +import { getSpacing } from '../../themeUtils'; +import { commonPalette } from '../palette'; +import { themeTypography } from '../typography'; + +export const navigationOverrides = { + // Menu + MuiMenuItem: { + styleOverrides: { + root: { + ...themeTypography.body2, + minHeight: getSpacing(4), + height: getSpacing(4), + transition: 'background-color 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + + '&:focus-visible': { + // Solves a known Mui issue: https://github.com/mui/material-ui/issues/23747 + backgroundColor: 'transparent', + + '&:hover': { + backgroundColor: commonPalette.action.hover + } + }, + '&.Mui-selected': { + color: commonPalette.primary.main, + + '&:focus-visible': { + // Solves a known Mui issue: https://github.com/mui/material-ui/issues/23747 + backgroundColor: commonPalette.primary.background + }, + '&:hover': { + backgroundColor: commonPalette.action.hover + }, + '& .MuiTypography-root, & .MuiSvgIcon-root': { + color: commonPalette.primary.main + } + }, + '&.Mui-disabled:empty': { + height: 0, + padding: 0 + }, + '& .MuiCheckbox-root, & > .MuiSvgIcon-root': { + marginRight: getSpacing(1) + } + }, + dense: { + minHeight: getSpacing(3), + height: getSpacing(3) + } + } + }, + + // Tabs + MuiTabs: { + defaultProps: { + TabIndicatorProps: { + classes: { + colorPrimary: 'colorPrimary' + } + } + }, + styleOverrides: { + root: ({ ownerState }) => ({ + boxSizing: 'content-box', + boxShadow: `0 1px 0 0 ${commonPalette.black[12]}` + }), + + vertical: { + borderBottom: 0 + } + } + }, + + // Tab + MuiTab: { + defaultProps: { + iconPosition: 'start' + }, + + styleOverrides: { + root: { + minHeight: getSpacing(6), + minWidth: getSpacing(6), + padding: getSpacing(0, 2), + paddingTop: '2px', + borderBottom: '2px solid transparent', + ...themeTypography.subtitle2, + color: commonPalette.text.primary, + transition: 'border 300ms cubic-bezier(0.4, 0, 0.2, 1)', + + '&:hover': { + borderBottomColor: commonPalette.text.primary + }, + '&.Mui-selected': { + pointerEvents: 'none', + + '& svg:not(.doNotFillIcon) path': { + fill: commonPalette.primary.main + } + }, + '.MuiTabs-vertical &': { + paddingTop: 0, + borderBottom: 0, + paddingLeft: '2px', + borderRight: '2px solid transparent', + + '&:hover': { + borderRightColor: commonPalette.text.primary + } + } + }, + wrapped: { + maxWidth: '240px' + } + } + }, + + // Breadcrumbs + MuiBreadcrumbs: { + styleOverrides: { + li: { + '& .MuiTypography-root': { + ...themeTypography.body2, + display: 'flex', + flexDirection: 'row', + alignItems: 'center' + }, + '& .MuiSvgIcon-root': { + fontSize: ICON_SIZE, + marginRight: getSpacing(1) + } + }, + + separator: { + marginLeft: getSpacing(0.5), + marginRight: getSpacing(0.5) + } + } + }, + + // Links + MuiLink: { + defaultProps: { + underline: 'hover' + } + } +}; diff --git a/packages/react-ui/src/theme/sections/components/surfaces.js b/packages/react-ui/src/theme/sections/components/surfaces.js new file mode 100644 index 000000000..aceddf9aa --- /dev/null +++ b/packages/react-ui/src/theme/sections/components/surfaces.js @@ -0,0 +1,38 @@ +import { getSpacing } from '../../themeUtils'; +import { APPBAR_SIZE } from '../../themeConstants'; +import { commonPalette } from '../palette'; +import { themeShadows } from '../shadows'; + +export const surfacesOverrides = { + // AppBar + MuiAppBar: { + styleOverrides: { + root: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + height: APPBAR_SIZE, + backgroundColor: commonPalette.brand.navyBlue, + color: commonPalette.common.white, + boxShadow: themeShadows[0], + + '& .MuiToolbar-root': { + justifyContent: 'space-between', + width: '100%', + padding: getSpacing(0, 1), + minHeight: APPBAR_SIZE + }, + '& .MuiTypography-root': { + color: commonPalette.common.white + }, + '& .MuiIconButton-root path': { + fill: commonPalette.common.white + }, + '& .MuiAvatar-root': { + width: getSpacing(4), + height: getSpacing(4) + } + } + } + } +}; diff --git a/packages/react-ui/src/theme/sections/cssBaseline.js b/packages/react-ui/src/theme/sections/cssBaseline.js new file mode 100644 index 000000000..4c5ed3b68 --- /dev/null +++ b/packages/react-ui/src/theme/sections/cssBaseline.js @@ -0,0 +1,90 @@ +import { getSpacing } from '../themeUtils'; +import { commonPalette } from './palette'; +import { themeTypography } from './typography'; + +export const CssBaseline = { + // Custom scrollbars + '*::-webkit-scrollbar': { + position: 'fixed', + width: '5px' + }, + '*::-webkit-scrollbar-track': { + boxShadow: 'none', + background: 'transparent' + }, + '*::-webkit-scrollbar-thumb': { + borderRadius: '3px', + background: commonPalette.action.focus, + outline: 'none' + }, + + // iOS Search clear button + 'input[type="search"]::-webkit-search-cancel-button': { + WebkitAppearance: 'none', + appearance: 'none', + height: getSpacing(2), + width: getSpacing(2), + display: 'block', + backgroundImage: `url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAn0lEQVR42u3UMQrDMBBEUZ9WfQqDmm22EaTyjRMHAlM5K+Y7lb0wnUZPIKHlnutOa+25Z4D++MRBX98MD1V/trSppLKHqj9TTBWKcoUqffbUcbBBEhTjBOV4ja4l4OIAZThEOV6jHO8ARXD+gPPvKMABinGOrnu6gTNUawrcQKNCAQ7QeTxORzle3+sDfjJpPCqhJh7GixZq4rHcc9l5A9qZ+WeBhgEuAAAAAElFTkSuQmCC)`, + backgroundRepeat: 'no-repeat', + backgroundSize: getSpacing(2) + }, + + // Mapbox controls + '.mapboxgl-ctrl.mapboxgl-ctrl-attrib': { + padding: getSpacing(0, 1), + borderRadius: getSpacing(0.5, 0, 0, 0), + + '& .mapboxgl-ctrl-attrib-inner': { + ...themeTypography.overline, + textTransform: 'none', + letterSpacing: '0.75px', + + '& a': { + color: commonPalette.primary.main + } + }, + + '&.mapboxgl-compact': { + backgroundColor: 'transparent', + right: getSpacing(0.5), + bottom: getSpacing(2.5), + + // Mobile + '@media (max-width: 600px)': { + bottom: getSpacing(0.5) + }, + + '& .mapboxgl-ctrl-attrib-button': { + backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20height='24'%20viewBox='0%200%2024%2024'%20width='24'%3E%3Cg%3E%3Crect%20fill='none'%20height='24'%20width='24'%20x='0'/%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Cg%3E%3Cpath%20d='M11.88,9.14c1.28,0.06,1.61,1.15,1.63,1.66h1.79c-0.08-1.98-1.49-3.19-3.45-3.19C9.64,7.61,8,9,8,12.14%20c0,1.94,0.93,4.24,3.84,4.24c2.22,0,3.41-1.65,3.44-2.95h-1.79c-0.03,0.59-0.45,1.38-1.63,1.44C10.55,14.83,10,13.81,10,12.14%20C10,9.25,11.28,9.16,11.88,9.14z%20M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z%20M12,20c-4.41,0-8-3.59-8-8%20s3.59-8,8-8s8,3.59,8,8S16.41,20,12,20z'%20fill='${commonPalette.text.secondary}'/%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`, + backgroundColor: 'rgba(255,255,255,.8)', + top: 'auto', + bottom: 0, + right: 0, + + '&:not(:disabled):hover': { + backgroundColor: 'rgba(255,255,255,.8)' + } + }, + + '& .mapboxgl-ctrl-attrib-inner': { + backgroundColor: 'rgba(255,255,255,.8)', + padding: getSpacing(0.5, 1), + borderRadius: getSpacing(1.5), + marginRight: getSpacing(2.5), + color: commonPalette.text.secondary + }, + + '&.mapboxgl-compact-show': { + '& .mapboxgl-ctrl-attrib-button': { + backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20height='24'%20viewBox='0%200%2024%2024'%20width='24'%3E%3Cpath%20d='M0%200h24v24H0z'%20fill='none'/%3E%3Cpath%20d='M19%206.41L17.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z'%20fill='white'/%3E%3C/svg%3E")`, + backgroundColor: commonPalette.common.black, + + '&:not(:disabled):hover': { + backgroundColor: commonPalette.common.black + } + } + } + } + } +}; diff --git a/packages/react-ui/src/theme/sections/palette.js b/packages/react-ui/src/theme/sections/palette.js new file mode 100644 index 000000000..c0d88c9c9 --- /dev/null +++ b/packages/react-ui/src/theme/sections/palette.js @@ -0,0 +1,195 @@ +import { alpha } from '@mui/material'; + +const COLOR_BLACK = '#2C3032'; +const COLOR_WHITE = '#FFFFFF'; + +const baseColors = { + common: { + black: COLOR_BLACK, + white: COLOR_WHITE + }, + neutral: { + 50: '#f8f9f9', + 100: '#e1e3e4', + 200: '#cbcdcf', + 300: '#b4b8ba', + 400: '#9da2a6', + 500: '#868d91', + 600: '#6f777c', + 700: '#595f63', + 800: '#43474a', + 900: COLOR_BLACK, + A100: '#ddddde', + A200: '#b9babb', + A400: '#7c7e7f', + A700: '#16191A' + }, + blue: { + 100: '#B9DAF9', + 200: '#5DB2F6', + 300: '#358BE7', + 400: '#036FE2', + 500: '#024D9E' + }, + green: { + 300: '#6BE2AD', + 400: '#47DB99', + 500: '#31996B' + }, + lightGreen: { + 300: '#8CB24A', + 400: '#709F1D', + 500: '#435F11' + }, + indigo: { + 300: '#34689F', + 400: '#024388', + 500: '#012C5A' + }, + orange: { + 300: '#F4B134', + 400: '#F29E02', + 500: '#A96E01' + }, + red: { + 300: '#CD593B', + 400: '#C1300B', + 500: '#872107' + }, + qualitative: { + // CARTO colors + // TODO: Related discussion https://app.shortcut.com/cartoteam/story/264834/ + bold: { + 0: '#7F3C8D', + 1: '#11A579', + 2: '#3969AC', + 3: '#F2B701', + 4: '#E73F74', + 5: '#80BA5A', + 6: '#E68310', + 7: '#008695', + 8: '#CF1C90', + 9: '#f97b72', + 10: '#4b4b8f', + 11: '#A5AA99' + } + }, + shades: { + dark: { + 90: alpha(COLOR_BLACK, 0.9), + 60: alpha(COLOR_BLACK, 0.6), + 40: alpha(COLOR_BLACK, 0.4), + 25: alpha(COLOR_BLACK, 0.25), + 12: alpha(COLOR_BLACK, 0.12), + 8: alpha(COLOR_BLACK, 0.08), + 4: alpha(COLOR_BLACK, 0.04) + }, + light: { + 90: alpha(COLOR_WHITE, 0.9), + 60: alpha(COLOR_WHITE, 0.6), + 40: alpha(COLOR_WHITE, 0.4), + 25: alpha(COLOR_WHITE, 0.25), + 12: alpha(COLOR_WHITE, 0.12), + 8: alpha(COLOR_WHITE, 0.08), + 4: alpha(COLOR_WHITE, 0.04) + } + } +}; + +export const commonPalette = { + common: { ...baseColors.common }, + primary: { + main: baseColors.blue[400], + dark: baseColors.blue[500], + light: baseColors.blue[300], + contrastText: baseColors.common.white, + background: alpha(baseColors.blue[400], 0.08), + relatedLight: '#EAF2FC' + }, + secondary: { + main: baseColors.green[400], + dark: baseColors.green[500], + light: baseColors.green[300], + contrastText: baseColors.common.black, + background: alpha(baseColors.green[400], 0.08), + relatedLight: '#EFFCF5' + }, + text: { + primary: baseColors.common.black, + secondary: baseColors.shades.dark[60], + disabled: baseColors.shades.dark[25], + hint: baseColors.shades.dark[40] + }, + background: { + paper: baseColors.common.white, + default: baseColors.neutral[50] + }, + action: { + active: baseColors.shades.dark[40], + hover: baseColors.shades.dark[8], + disabledBackground: baseColors.shades.dark[12], + disabled: baseColors.shades.dark[25], + selected: baseColors.shades.dark[12], + focus: baseColors.shades.dark[12] + }, + info: { + main: baseColors.indigo[400], + dark: baseColors.indigo[500], + light: baseColors.indigo[300], + contrastText: baseColors.common.white, + relatedDark: '#0D2B4A', + relatedLight: '#E9EEF4' + }, + success: { + main: baseColors.lightGreen[400], + dark: baseColors.lightGreen[500], + light: baseColors.lightGreen[300], + contrastText: baseColors.common.white, + relatedDark: '#3D541A', + relatedLight: '#F2F5EB' + }, + warning: { + main: baseColors.orange[400], + dark: baseColors.orange[500], + light: baseColors.orange[300], + contrastText: baseColors.common.black, + relatedDark: '#78540F', + relatedLight: '#FEF6EA' + }, + error: { + main: baseColors.red[400], + light: baseColors.red[300], + dark: baseColors.red[500], + contrastText: baseColors.common.white, + relatedDark: '#622215', + relatedLight: '#F9EDEA' + }, + grey: { + ...baseColors.neutral + }, + divider: baseColors.shades.dark[12], + + // Custom common colors + default: { + main: baseColors.neutral[100], + dark: baseColors.neutral[200], + light: baseColors.neutral[50], + outlinedBorder: baseColors.shades.dark[25], + background: baseColors.shades.dark[4] + }, + brand: { + navyBlue: '#162945', + locationRed: '#EB1510', + predictionBlue: '#1785FB', + softBlue: '#F2F6F9' + }, + white: { + ...baseColors.shades.light + }, + black: { + ...baseColors.shades.dark + }, + qualitative: { + ...baseColors.qualitative + } +}; diff --git a/packages/react-ui/src/theme/sections/shadows.js b/packages/react-ui/src/theme/sections/shadows.js new file mode 100644 index 000000000..9d472f11c --- /dev/null +++ b/packages/react-ui/src/theme/sections/shadows.js @@ -0,0 +1,29 @@ +// We only use some of them (the ones with a comment with their number), but all of them are necessary for the Mui theme. + +export const themeShadows = [ + 'none', // 0 + '0px 2px 1px -1px rgba(44, 48, 50, 0.12), 0px 1px 1px rgba(44, 48, 50, 0.08), 0px 1px 3px rgba(44, 48, 50, 0.04)', // 1 + '0px 3px 1px -2px rgba(44, 48, 50, 0.12), 0px 2px 2px rgba(44, 48, 50, 0.08), 0px 1px 5px rgba(44, 48, 50, 0.04)', // 2 + '0px 3px 3px -2px rgba(0,0,0,0.16),0px 3px 4px 0px rgba(0,0,0,0.08),0px 1px 8px 0px rgba(0,0,0,0.04)', + '0px 2px 4px -1px rgba(44, 48, 50, 0.12), 0px 4px 5px rgba(44, 48, 50, 0.08), 0px 1px 10px rgba(44, 48, 50, 0.04)', // 4 + '0px 3px 5px -1px rgba(0,0,0,0.16),0px 5px 8px 0px rgba(0,0,0,0.08),0px 1px 14px 0px rgba(0,0,0,0.04)', + '0px 3px 5px -1px rgba(44, 48, 50, 0.12), 0px 6px 10px rgba(44, 48, 50, 0.08), 0px 1px 18px rgba(44, 48, 50, 0.04)', // 6 + '0px 4px 5px -2px rgba(0,0,0,0.16),0px 7px 10px 1px rgba(0,0,0,0.08),0px 2px 16px 1px rgba(0,0,0,0.04)', + '0px 5px 5px -3px rgba(44, 48, 50, 0.12), 0px 8px 10px 1px rgba(44, 48, 50, 0.08), 0px 3px 14px 2px rgba(44, 48, 50, 0.04)', // 8 + '0px 5px 6px -3px rgba(0,0,0,0.16),0px 9px 12px 1px rgba(0,0,0,0.08),0px 3px 16px 2px rgba(0,0,0,0.04)', + '0px 6px 6px -3px rgba(0,0,0,0.16),0px 10px 14px 1px rgba(0,0,0,0.08),0px 4px 18px 3px rgba(0,0,0,0.04)', + '0px 6px 7px -4px rgba(0,0,0,0.16),0px 11px 15px 1px rgba(0,0,0,0.08),0px 4px 20px 3px rgba(0,0,0,0.04)', + '0px 7px 8px -4px rgba(0,0,0,0.16),0px 12px 17px 2px rgba(0,0,0,0.08),0px 5px 22px 4px rgba(0,0,0,0.04)', + '0px 7px 8px -4px rgba(0,0,0,0.16),0px 13px 19px 2px rgba(0,0,0,0.08),0px 5px 24px 4px rgba(0,0,0,0.04)', + '0px 7px 9px -4px rgba(0,0,0,0.16),0px 14px 21px 2px rgba(0,0,0,0.08),0px 5px 26px 4px rgba(0,0,0,0.04)', + '0px 8px 9px -5px rgba(0,0,0,0.16),0px 15px 22px 2px rgba(0,0,0,0.08),0px 6px 28px 5px rgba(0,0,0,0.04)', + '0px 8px 10px -5px rgba(44, 48, 50, 0.12), 0px 16px 24px 2px rgba(44, 48, 50, 0.08), 0px 6px 30px 5px rgba(44, 48, 50, 0.04)', // 16 + '0px 8px 11px -5px rgba(0,0,0,0.16),0px 17px 26px 2px rgba(0,0,0,0.08),0px 6px 32px 5px rgba(0,0,0,0.04)', + '0px 9px 11px -5px rgba(0,0,0,0.16),0px 18px 28px 2px rgba(0,0,0,0.08),0px 7px 34px 6px rgba(0,0,0,0.04)', + '0px 9px 12px -6px rgba(0,0,0,0.16),0px 19px 29px 2px rgba(0,0,0,0.08),0px 7px 36px 6px rgba(0,0,0,0.04)', + '0px 10px 13px -6px rgba(0,0,0,0.16),0px 20px 31px 3px rgba(0,0,0,0.08),0px 8px 38px 7px rgba(0,0,0,0.04)', + '0px 10px 13px -6px rgba(0,0,0,0.16),0px 21px 33px 3px rgba(0,0,0,0.08),0px 8px 40px 7px rgba(0,0,0,0.04)', + '0px 10px 14px -6px rgba(0,0,0,0.16),0px 22px 35px 3px rgba(0,0,0,0.08),0px 8px 42px 7px rgba(0,0,0,0.04)', + '0px 11px 14px -7px rgba(0,0,0,0.16),0px 23px 36px 3px rgba(0,0,0,0.08),0px 9px 44px 8px rgba(0,0,0,0.04)', + '0px 11px 15px -7px rgba(44, 48, 50, 0.12), 0px 24px 38px 3px rgba(44, 48, 50, 0.08), 0px 9px 46px 8px rgba(44, 48, 50, 0.04)' // 24 +]; diff --git a/packages/react-ui/src/theme/sections/typography.js b/packages/react-ui/src/theme/sections/typography.js new file mode 100644 index 000000000..e30ef0c16 --- /dev/null +++ b/packages/react-ui/src/theme/sections/typography.js @@ -0,0 +1,145 @@ +import { getPixelToRem } from '../themeUtils'; + +const baseTypography = { + htmlFontSize: 16, + fontFamily: 'Inter, sans-serif', + fontSize: 16, + lineHeight: 1.5, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightBold: 600, + fontSmoothing: 'antialiased', + h1: { + fontFamily: 'Inter, sans-serif', + fontWeight: 600, + fontSize: getPixelToRem(96), + lineHeight: 1.167, + letterSpacing: '-1.5px' + }, + h2: { + fontFamily: 'Inter, sans-serif', + fontWeight: 600, + fontSize: getPixelToRem(60), + lineHeight: 1.2, + letterSpacing: '-0.5px' + }, + h3: { + fontFamily: 'Inter, sans-serif', + fontWeight: 600, + fontSize: getPixelToRem(48), + lineHeight: 1.167, + letterSpacing: 0 + }, + h4: { + fontFamily: 'Inter, sans-serif', + fontWeight: 600, + fontSize: getPixelToRem(32), + lineHeight: 1.25, + letterSpacing: '0.15px' + }, + h5: { + fontFamily: 'Inter, sans-serif', + fontWeight: 600, + fontSize: getPixelToRem(24), + lineHeight: 1.167, + letterSpacing: '0.15px' + }, + h6: { + fontFamily: 'Inter, sans-serif', + fontWeight: 500, + fontSize: getPixelToRem(18), + lineHeight: 1.333, + letterSpacing: '0.15px' + }, + subtitle1: { + fontFamily: 'Inter, sans-serif', + fontWeight: 500, + fontSize: getPixelToRem(16), + lineHeight: 1.5, + letterSpacing: '0.15px' + }, + subtitle2: { + fontFamily: 'Inter, sans-serif', + fontWeight: 600, + fontSize: getPixelToRem(13), + lineHeight: 1.538, + letterSpacing: '0.1px' + }, + body1: { + fontFamily: 'Inter, sans-serif', + fontWeight: 400, + fontSize: getPixelToRem(16), + lineHeight: 1.5, + letterSpacing: '0.4px' + }, + body2: { + fontFamily: 'Inter, sans-serif', + fontWeight: 400, + fontSize: getPixelToRem(13), + lineHeight: 1.538, + letterSpacing: '0.25px' + }, + button: { + fontFamily: 'Inter, sans-serif', + fontWeight: 500, + fontSize: getPixelToRem(13), + lineHeight: 1.538, + letterSpacing: '0.25px', + textTransform: 'inherit' + }, + caption: { + fontFamily: 'Inter, sans-serif', + fontWeight: 400, + fontSize: getPixelToRem(11), + lineHeight: 1.454, + letterSpacing: '0.2px' + }, + overline: { + fontFamily: 'Inter, sans-serif', + fontWeight: 500, + fontSize: getPixelToRem(10), + lineHeight: 1.2, + letterSpacing: '1.2px', + textTransform: 'uppercase' + } +}; + +const customTypography = { + captionMedium: { + ...baseTypography.caption, + fontWeight: 500, + letterSpacing: '0.4px' + }, + overlineDelicate: { + ...baseTypography.overline, + fontWeight: 400, + letterSpacing: '1.2px' + }, + code1: { + fontFamily: '"Overpass Mono", monospace', + fontWeight: 400, + fontSize: getPixelToRem(16), + lineHeight: 1.5, + letterSpacing: 0 + }, + code2: { + fontFamily: '"Overpass Mono", monospace', + fontWeight: 400, + fontSize: getPixelToRem(14), + lineHeight: 1.428, + letterSpacing: 0 + }, + code3: { + fontFamily: '"Overpass Mono", monospace', + fontWeight: 400, + fontSize: getPixelToRem(12), + lineHeight: 1.333, + letterSpacing: 0 + } +}; + +export const themeTypography = { + ...baseTypography, + ...customTypography +}; diff --git a/packages/react-ui/src/theme/themeConstants.js b/packages/react-ui/src/theme/themeConstants.js new file mode 100644 index 000000000..894b405d9 --- /dev/null +++ b/packages/react-ui/src/theme/themeConstants.js @@ -0,0 +1,20 @@ +import { getSpacing } from './themeUtils'; + +// Common +export const SPACING = 8; + +// Breakpoints +export const BREAKPOINTS = { + XS: 320, + SM: 600, + MD: 960, + LG: 1280, + XL: 1600 +}; + +// Icons +export const ICON_SIZE = getSpacing(2.25); +export const ICON_SIZE_M = getSpacing(3); + +// AppBar +export const APPBAR_SIZE = getSpacing(6); diff --git a/packages/react-ui/src/theme/themeUtils.js b/packages/react-ui/src/theme/themeUtils.js new file mode 100644 index 000000000..9675338ff --- /dev/null +++ b/packages/react-ui/src/theme/themeUtils.js @@ -0,0 +1,13 @@ +import { createSpacing } from '@mui/system'; +import { SPACING } from './themeConstants'; + +// Create spacing for theming +export const getSpacing = createSpacing(SPACING); + +// Convert pixels to rem +export function getPixelToRem(px) { + const fontBase = 16; + const rem = (1 / fontBase) * px + 'rem'; + + return rem; +} diff --git a/packages/react-ui/src/types.d.ts b/packages/react-ui/src/types.d.ts index f086fe63a..3d57d94c1 100644 --- a/packages/react-ui/src/types.d.ts +++ b/packages/react-ui/src/types.d.ts @@ -1,4 +1,10 @@ import { GroupDateTypes } from '@carto/react-core'; +import { + AppBarProps as MuiAppBarProps, + TextFieldProps, + TypographyProps as MuiTypographyProps +} from '@mui/material'; +import { CSSProperties } from 'react'; export type WrapperWidgetUI = { title: string; @@ -49,7 +55,7 @@ export type HistogramWidgetUI = { export type BarWidgetUI = { xAxisData: (string | number)[]; yAxisData: (string | number)[] | (string | number)[][]; - series?: string[] + series?: string[]; colors?: string | string[]; stacked?: boolean; labels?: object; @@ -192,7 +198,7 @@ export type LegendRamp = { export type AnimationOptions = { duration?: number; animateOnMount?: boolean; - initialValue?: number + initialValue?: number; }; export type AnimatedNumber = { @@ -219,7 +225,7 @@ export type ComparativeFormulaWidgetUI = { export enum ORDER_TYPES { RANKING = 'ranking', - FIXED = 'fixed', + FIXED = 'fixed' } type CategoryData = { @@ -261,3 +267,54 @@ export type ComparativePieWidgetUIProps = { selectedCategories?: string[]; onCategorySelected?: (categories: string[]) => any; }; + +// Typography +export interface TypographyProps extends MuiTypographyProps { + weight?: 'regular' | 'medium' | 'strong'; + italic?: boolean; + style?: CSSProperties; +} + +// Tooltip data +// Export types and component if we need it outsite C4R +type TooltipDataProps = { + items: [ + { + category?: string; + value: string | number; + outlinedBullet?: boolean; + color?: 'primary' | 'secondary'; + } + ]; + title?: string; +}; + +// SelectField +export interface SelectFieldProps extends TextFieldProps { + items: [ + { + label: string; + value: string | number; + } + ]; + multiple?: boolean; + placeholder: string; + size?: 'small' | 'medium'; +} + +// UploadField +export interface UploadFieldProps extends TextFieldProps { + buttonText?: string; + accept?: string[]; + files?: []; + onChange: (file?: File | null) => void; +} + +// AppBar +export interface AppBarProps extends MuiAppBarProps { + brandLogo?: React.ReactElement; + brandText?: string | React.ReactElement; + secondaryText?: string | React.ReactElement; + onClickMenu?: Function; + showBurgerMenu?: boolean; +} diff --git a/packages/react-ui/src/widgets/BarWidgetUI.js b/packages/react-ui/src/widgets/BarWidgetUI.js index 2404ebcc3..feddb2b97 100644 --- a/packages/react-ui/src/widgets/BarWidgetUI.js +++ b/packages/react-ui/src/widgets/BarWidgetUI.js @@ -1,22 +1,20 @@ import React, { useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import ReactEcharts from '../custom-components/echarts-for-react'; -import { Grid, Link, Typography, useTheme, makeStyles, darken } from '@material-ui/core'; +import { Grid, Link, useTheme, darken } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import detectTouchScreen from './utils/detectTouchScreen'; import { processFormatterRes } from './utils/formatterUtils'; +import Typography from '../components/atoms/Typography'; const IS_TOUCH_SCREEN = detectTouchScreen(); const useStyles = makeStyles((theme) => ({ optionsSelectedBar: { - marginBottom: theme.spacing(2), + marginBottom: theme.spacingValue * 2, '& .MuiTypography-caption': { color: theme.palette.text.secondary - }, - - '& .MuiButton-label': { - ...theme.typography.caption } }, @@ -58,15 +56,14 @@ function BarWidgetUI(props) { () => ({ show: tooltip, trigger: 'axis', - padding: [theme.spacing(0.5), theme.spacing(1)], + padding: [theme.spacingValue * 0.5, theme.spacingValue], borderWidth: 0, textStyle: { ...theme.typography.caption, - fontSize: 12, - lineHeight: 16, + fontSize: 11, color: theme.palette.common.white }, - backgroundColor: theme.palette.other.tooltip, + backgroundColor: theme.palette.black[90], position: function (point, _params, _dom, _rect, size) { const position = { top: 0 }; @@ -101,8 +98,8 @@ function BarWidgetUI(props) { show: false }, axisLabel: { - ...theme.typography.charts, - padding: [theme.spacing(0.5), 0, 0, 0] + ...theme.typography.overlineDelicate, + padding: [theme.spacingValue * 0.5, 0, 0, 0] }, data: xAxisDataWithLabels }), @@ -132,14 +129,13 @@ function BarWidgetUI(props) { axisLabel: { margin: 0, verticalAlign: 'bottom', - padding: [0, 0, theme.typography.charts.fontSize, 0], + padding: [0, 0, theme.spacingValue * 1.25, 0], show: true, showMaxLabel: true, showMinLabel: false, inside: true, - color: (value) => - value >= maxValue ? theme.palette.charts.maxLabel : 'transparent', - ...theme.typography.charts, + color: (value) => (value >= maxValue ? theme.palette.black[60] : 'transparent'), + ...theme.typography.overlineDelicate, formatter: (v) => processFormatterRes(yAxisFormatter(v)) }, axisLine: { @@ -152,15 +148,15 @@ function BarWidgetUI(props) { show: true, onZero: false, lineStyle: { - color: theme.palette.charts.axisLine + color: theme.palette.black[4] } } }), [ maxValue, - theme.palette.charts.axisLine, - theme.palette.charts.maxLabel, - theme.typography.charts, + theme.palette.black, + theme.typography.overlineDelicate, + theme.spacingValue, yAxisFormatter ] ); @@ -182,7 +178,7 @@ function BarWidgetUI(props) { return { value, ...(isDisabled && { - itemStyle: { color: theme.palette.charts.disabled }, + itemStyle: { color: theme.palette.black[25] }, disabled: true }) }; @@ -207,7 +203,7 @@ function BarWidgetUI(props) { xAxisDataWithLabels.length >= 4 ? calculateMargin(xAxisDataWithLabels[0], xAxisDataWithLabels.length) : 0, - top: theme.spacing(2), + top: theme.spacingValue * 2, right: xAxisDataWithLabels.length >= 4 ? calculateMargin( @@ -215,12 +211,12 @@ function BarWidgetUI(props) { xAxisDataWithLabels.length ) : 0, - bottom: theme.spacing(0), + bottom: 0, containLabel: true }, axisPointer: { lineStyle: { - color: theme.palette.charts.axisPointer + color: theme.palette.black[40] } }, color: colors, @@ -307,7 +303,11 @@ function BarWidgetUI(props) { {selectedBars?.length || 'All'} selected {selectedBars && selectedBars.length > 0 && ( - clearBars()}> + clearBars()} + underline='hover' + > Clear )} @@ -441,7 +441,7 @@ function useProcessedProps({ return { ...props, labels, - height: height ?? theme.spacing(22), + height: height ?? theme.spacingValue * 22, selectedBars: formatSelectedBars(_selectedBars), yAxisData, colors, diff --git a/packages/react-ui/src/widgets/CategoryWidgetUI.js b/packages/react-ui/src/widgets/CategoryWidgetUI.js index 6fc1f5c4e..8ab24fef8 100644 --- a/packages/react-ui/src/widgets/CategoryWidgetUI.js +++ b/packages/react-ui/src/widgets/CategoryWidgetUI.js @@ -9,13 +9,13 @@ import { Divider, SvgIcon, TextField, - Typography, - makeStyles, Tooltip -} from '@material-ui/core'; -import { Skeleton } from '@material-ui/lab'; +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { Skeleton } from '@mui/material'; import { animateValues } from './utils/animations'; +import Typography from '../components/atoms/Typography'; const useStyles = makeStyles((theme) => ({ root: { @@ -32,21 +32,21 @@ const useStyles = makeStyles((theme) => ({ cursor: 'pointer', flexWrap: 'nowrap', - '&:hover $progressbar div': { + '&:hover .progressbar div': { backgroundColor: theme.palette.secondary.dark } }, element: { - '&$unselected': { + '&.unselected': { color: theme.palette.text.disabled, - '& $progressbar div': { + '& .progressbar div': { backgroundColor: theme.palette.text.disabled } }, - '&$rest $progressbar div': { + '&.rest .progressbar div': { backgroundColor: theme.palette.text.disabled } }, @@ -91,10 +91,6 @@ const useStyles = makeStyles((theme) => ({ '& .MuiTypography-caption': { color: theme.palette.text.secondary - }, - - '& .MuiButton-label': { - ...theme.typography.caption } }, @@ -505,21 +501,37 @@ function CategoryWidgetUI(props) { {selectedCategories.length ? selectedCategories.length : 'All'} selected {showAll ? ( - + Apply ) : blockedCategories.length > 0 ? ( - + Unlock ) : ( selectedCategories.length > 0 && ( - + Lock - + Clear diff --git a/packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js b/packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js index 11c5d98fa..641121a35 100644 --- a/packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js +++ b/packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js @@ -6,15 +6,15 @@ import { Chip, Divider, IconButton, - makeStyles, Menu, MenuItem, Tooltip, - Typography, useTheme -} from '@material-ui/core'; -import { ArrowDropDown } from '@material-ui/icons'; +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { ArrowDropDown } from '@mui/icons-material'; import PropTypes from 'prop-types'; +import Typography from '../components/atoms/Typography'; function FeatureSelectionWidgetUI({ className, @@ -125,9 +125,7 @@ function Helper({ hasMode, enabled, isEdit, children }) { return ( @@ -149,7 +147,6 @@ function GeometryViewer({ modeId === selectedMode); if (!foundMode) { - throw new Error( - 'Selected mode not supported' - ); + throw new Error('Selected mode not supported'); } return foundMode; } else { @@ -199,8 +194,8 @@ function SelectedModeViewer({ const onEnabledChangeWrapper = () => onEnabledChange(!enabled); return ( - - + + {icon} @@ -276,7 +271,7 @@ function ModesSelector({ return ( - + diff --git a/packages/react-ui/src/widgets/FormulaWidgetUI.js b/packages/react-ui/src/widgets/FormulaWidgetUI.js index bc52e7460..92c3b5048 100644 --- a/packages/react-ui/src/widgets/FormulaWidgetUI.js +++ b/packages/react-ui/src/widgets/FormulaWidgetUI.js @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; -import { Box, makeStyles } from '@material-ui/core'; +import { Box } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { animateValue } from './utils/animations'; const useStyles = makeStyles((theme) => ({ @@ -13,7 +14,7 @@ const useStyles = makeStyles((theme) => ({ color: theme.palette.text.secondary, marginLeft: theme.spacing(0.5), - '&$before': { + '&.before': { marginLeft: 0, marginRight: theme.spacing(0.5) } diff --git a/packages/react-ui/src/widgets/HistogramWidgetUI/HistogramWidgetUI.js b/packages/react-ui/src/widgets/HistogramWidgetUI/HistogramWidgetUI.js index 9d0e87cad..d5e625904 100644 --- a/packages/react-ui/src/widgets/HistogramWidgetUI/HistogramWidgetUI.js +++ b/packages/react-ui/src/widgets/HistogramWidgetUI/HistogramWidgetUI.js @@ -2,10 +2,12 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { useMemo } from 'react'; import ReactEcharts from '../../custom-components/echarts-for-react'; -import { darken, Grid, Link, makeStyles, Typography, useTheme } from '@material-ui/core'; +import { darken, Grid, Link, useTheme } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { processFormatterRes } from '../utils/formatterUtils'; import detectTouchscreen from '../utils/detectTouchScreen'; import useHistogramInteractivity from './useHistogramInteractivity'; +import Typography from '../../components/atoms/Typography'; const IS_TOUCH_SCREEN = detectTouchscreen(); @@ -15,10 +17,6 @@ const useStyles = makeStyles((theme) => ({ '& .MuiTypography-caption': { color: theme.palette.text.secondary - }, - - '& .MuiButton-label': { - ...theme.typography.caption } }, clearButton: { @@ -68,15 +66,14 @@ function HistogramWidgetUI({ () => ({ show: tooltip, trigger: 'item', - padding: [theme.spacing(0.5), theme.spacing(1)], + padding: [theme.spacingValue * 0.5, theme.spacingValue], borderWidth: 0, textStyle: { ...theme.typography.caption, - fontSize: 12, - lineHeight: 16, + fontSize: 11, color: theme.palette.common.white }, - backgroundColor: theme.palette.other.tooltip, + backgroundColor: theme.palette.black[90], confine: true, position: 'top', formatter(params) { @@ -98,7 +95,7 @@ function HistogramWidgetUI({ splitLine: { show: true, lineStyle: { - color: theme.palette.charts.axisLine + color: theme.palette.black[4] } }, axisTick: { @@ -107,9 +104,14 @@ function HistogramWidgetUI({ axisLabel: { showMinLabel: true, showMaxLabel: true, - ...theme.typography.charts, + ...theme.typography.overlineDelicate, hideOverlap: true, - padding: [theme.spacing(0.5), theme.spacing(0.5), 0, theme.spacing(0.5)], + padding: [ + theme.spacingValue * 0.5, + theme.spacingValue * 0.5, + 0, + theme.spacingValue * 0.5 + ], formatter: (value) => { const formattedValue = processFormatterRes(xAxisFormatter(value)); return value === min @@ -118,7 +120,7 @@ function HistogramWidgetUI({ ? formatMax(formattedValue) : formattedValue; }, - color: theme.palette.charts.maxLabel + color: theme.palette.black[60] } }), [min, max, formattedData.length, theme, xAxisFormatter] @@ -134,7 +136,7 @@ function HistogramWidgetUI({ splitLine: { show: true, lineStyle: { - color: theme.palette.charts.axisLine + color: theme.palette.black[4] } }, axisTick: { @@ -143,7 +145,7 @@ function HistogramWidgetUI({ axisLabel: { margin: 0, verticalAlign: 'bottom', - padding: [0, 0, theme.typography.charts.fontSize, 0], + padding: [0, 0, theme.spacingValue * 1.25, 0], show: true, showMaxLabel: true, showMinLabel: false, @@ -153,19 +155,19 @@ function HistogramWidgetUI({ Math.max(...data.map((d) => d ?? Number.MIN_SAFE_INTEGER)) || 1; let col = 'transparent'; if (value >= maxValue) { - col = theme.palette.charts.maxLabel; + col = theme.palette.black[60]; } return col; }, - ...theme.typography.charts, + ...theme.typography.overlineDelicate, formatter: (v) => processFormatterRes(yAxisFormatter(v)) } }), [ - theme.palette.charts.axisLine, - theme.palette.charts.maxLabel, - theme.typography.charts, + theme.palette.black, + theme.spacingValue, + theme.typography.overlineDelicate, data, yAxisFormatter ] @@ -175,9 +177,7 @@ function HistogramWidgetUI({ const seriesOptions = useMemo(() => { const dataWithColor = formattedData.map((item, idx) => { const isDisabled = selectedBars.length && selectedBars.indexOf(idx) === -1; - const color = isDisabled - ? theme.palette.charts.disabled - : theme.palette.secondary.main; + const color = isDisabled ? theme.palette.black[25] : theme.palette.secondary.main; return { value: item, itemStyle: { color } }; }); @@ -226,7 +226,7 @@ function HistogramWidgetUI({ }, [ formattedData, markAreaOptions, - theme.palette.charts.disabled, + theme.palette.black, theme.palette.secondary.main, selectedBars, animation @@ -235,10 +235,10 @@ function HistogramWidgetUI({ const options = useMemo( () => ({ grid: { - left: theme.spacing(0.1), - right: theme.spacing(0.1), - top: theme.spacing(2), - bottom: theme.spacing(0.5), + left: theme.spacingValue * 0.1, + right: theme.spacingValue * 0.1, + top: theme.spacingValue * 2, + bottom: theme.spacingValue * 0.5, containLabel: true }, tooltip: tooltipOptions, @@ -264,13 +264,14 @@ function HistogramWidgetUI({ alignItems='center' className={classes.optionsSelectedBar} > - + {selectedBars.length ? yAxisFormatter(countSelectedElements) : 'All'} selected {selectedBars.length > 0 && ( onSelectedBarsChange([])} + underline='hover' > Clear @@ -346,7 +347,9 @@ function defaultTooltipFormatter(params, xAxisFormatter, yAxisFormatter) { } const [left, right, value] = params.data.value; - const title = `${processFormatterRes(xAxisFormatter(left))} — ${processFormatterRes( + const title = `${processFormatterRes( + xAxisFormatter(left) + )} — ${processFormatterRes( xAxisFormatter(right) )}`; const formattedValue = processFormatterRes(yAxisFormatter(value)); diff --git a/packages/react-ui/src/widgets/HistogramWidgetUI/useHistogramInteractivity.js b/packages/react-ui/src/widgets/HistogramWidgetUI/useHistogramInteractivity.js index 26f558c90..7c6c59369 100644 --- a/packages/react-ui/src/widgets/HistogramWidgetUI/useHistogramInteractivity.js +++ b/packages/react-ui/src/widgets/HistogramWidgetUI/useHistogramInteractivity.js @@ -1,4 +1,4 @@ -import { useTheme } from '@material-ui/core'; +import { useTheme } from '@mui/material'; import { useCallback, useEffect, useMemo, useState } from 'react'; const events = {}; diff --git a/packages/react-ui/src/widgets/NoDataAlert.js b/packages/react-ui/src/widgets/NoDataAlert.js index 3291e7b2d..91fcbe6ba 100644 --- a/packages/react-ui/src/widgets/NoDataAlert.js +++ b/packages/react-ui/src/widgets/NoDataAlert.js @@ -1,22 +1,30 @@ import React from 'react'; -import { Alert, AlertTitle } from '@material-ui/lab'; -import { Box, Typography } from '@material-ui/core'; +import { Alert, AlertTitle } from '@mui/material'; +import { Box } from '@mui/material'; +import Typography from '../components/atoms/Typography'; -function AlertBody ({ color = undefined, children }) { +function AlertBody({ color = undefined, children }) { return children ? ( - - {children} - -) : ( - -)} + + + {children} + + + ) : ( + + ); +} function NoDataAlert({ title = 'No data available', body = 'There are no results for the combination of filters applied to your data. Try tweaking your filters, or zoom and pan the map to adjust the Map View.', severity = undefined }) { - return severity ? ( {title && {title}} @@ -24,10 +32,10 @@ function NoDataAlert({ ) : ( - {title && {title}} - {body} + {title && {title}} + {body} - ) + ); } export default NoDataAlert; diff --git a/packages/react-ui/src/widgets/OpacityControl.js b/packages/react-ui/src/widgets/OpacityControl.js index 693e73b5e..98169f7b7 100644 --- a/packages/react-ui/src/widgets/OpacityControl.js +++ b/packages/react-ui/src/widgets/OpacityControl.js @@ -1,12 +1,6 @@ import React from 'react'; -import { - Box, - Grid, - InputAdornment, - makeStyles, - Slider, - TextField -} from '@material-ui/core'; +import { Box, Grid, InputAdornment, Slider, TextField } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import LayerOptionWrapper from './legend/LayerOptionWrapper'; const useOpacityControlStyles = makeStyles(({ spacing }) => ({ @@ -64,7 +58,7 @@ export default function OpacityControl({ opacity, onChangeOpacity }) { ({ selectedMode: false, type: 'scroll', - left: theme.spacing(1), - bottom: -theme.spacing(0.5), - itemGap: theme.spacing(3), + left: theme.spacingValue, + bottom: theme.spacingValue * -0.5, + itemGap: theme.spacingValue * 3, icon: 'circle', - itemWidth: theme.spacing(1), - itemHeight: theme.spacing(1), + itemWidth: theme.spacingValue, + itemHeight: theme.spacingValue, // TODO: as prop? formatter: (name) => name.toUpperCase(), textStyle: { - ...theme.typography.charts, + ...theme.typography.overlineDelicate, color: theme.palette.text.primary, lineHeight: 1, verticalAlign: 'bottom', - padding: [0, 0, 0, theme.spacing(0.5)] + padding: [0, 0, 0, theme.spacingValue * 0.5] }, inactiveColor: theme.palette.text.disabled, pageIcons: { @@ -82,14 +82,12 @@ function PieWidgetUI({ 'path://M9 16.59 13.3265857 12 9 7.41 10.3319838 6 16 12 10.3319838 18z' ] }, - pageIconSize: theme.spacing(1.5), + pageIconSize: theme.spacingValue * 1.5, pageIconColor: theme.palette.text.secondary, pageIconInactiveColor: theme.palette.text.disabled, pageTextStyle: { - fontFamily: theme.typography.charts.fontFamily, - fontSize: theme.spacing(1.5), - lineHeight: theme.spacing(1.75), - fontWeight: 'normal', + fontFamily: theme.typography.overlineDelicate.fontFamily, + fontSize: 10, color: theme.palette.text.primary } }), @@ -103,16 +101,16 @@ function PieWidgetUI({ position: 'center', rich: { b: { - fontFamily: theme.typography.charts.fontFamily, - fontSize: theme.spacing(1.75), - lineHeight: theme.spacing(1.75), + fontFamily: theme.typography.overlineDelicate.fontFamily, + fontSize: theme.spacingValue * 1.75, + lineHeight: theme.spacingValue * 1.75, fontWeight: 'normal', color: theme.palette.text.primary }, per: { ...theme.typography, - fontSize: theme.spacing(3), - lineHeight: theme.spacing(4.5), + fontSize: theme.spacingValue * 3, + lineHeight: theme.spacingValue * 4.5, fontWeight: 600, color: theme.palette.text.primary } @@ -133,7 +131,7 @@ function PieWidgetUI({ const disabled = selectedCategories?.length && !selectedCategories.includes(clonedItem.name); - + if (labels?.[clonedItem.name]) { clonedItem.name = labels[clonedItem.name]; } @@ -150,7 +148,7 @@ function PieWidgetUI({ radius: ['74%', '90%'], selectedOffset: 0, hoverOffset: 5, - bottom: theme.spacing(2.5), + bottom: theme.spacingValue * 2.5, label: { show: showLabel, ...labelOptions }, emphasis: { label: { ...labelOptions, position: undefined } @@ -172,16 +170,16 @@ function PieWidgetUI({ const options = useMemo( () => ({ grid: { - left: theme.spacing(0), - top: theme.spacing(0), - right: theme.spacing(0), - bottom: theme.spacing(0) + left: 0, + top: 0, + right: 0, + bottom: 0 }, tooltip: tooltipOptions, legend: legendOptions, series: seriesOptions }), - [theme, tooltipOptions, seriesOptions, legendOptions] + [tooltipOptions, seriesOptions, legendOptions] ); const clickEvent = useCallback( diff --git a/packages/react-ui/src/widgets/RangeWidgetUI.js b/packages/react-ui/src/widgets/RangeWidgetUI.js index 4ce72e9ea..e0f329a4b 100644 --- a/packages/react-ui/src/widgets/RangeWidgetUI.js +++ b/packages/react-ui/src/widgets/RangeWidgetUI.js @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import { Box, Link, makeStyles, Slider, TextField } from '@material-ui/core'; +import { Box, Link, Slider, TextField } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { debounce } from '@carto/react-core'; const useStyles = makeStyles((theme) => ({ @@ -155,7 +156,7 @@ function RangeWidgetUI({ data, min, max, limits, onSelectedRangeChange }) { {hasBeenModified && ( - + Clear )} @@ -193,7 +194,7 @@ function RangeWidgetUI({ data, min, max, limits, onSelectedRangeChange }) { handleInputChange(event, 0)} onBlur={() => handleInputBlur(0)} inputProps={{ @@ -206,7 +207,7 @@ function RangeWidgetUI({ data, min, max, limits, onSelectedRangeChange }) { handleInputChange(event, 1)} onBlur={() => handleInputBlur(1)} inputProps={{ diff --git a/packages/react-ui/src/widgets/ScatterPlotWidgetUI.js b/packages/react-ui/src/widgets/ScatterPlotWidgetUI.js index 57ae11bee..1594782b5 100644 --- a/packages/react-ui/src/widgets/ScatterPlotWidgetUI.js +++ b/packages/react-ui/src/widgets/ScatterPlotWidgetUI.js @@ -1,4 +1,4 @@ -import { useTheme } from '@material-ui/core'; +import { useTheme } from '@mui/material'; import PropTypes from 'prop-types'; import React, { useRef, useState, useEffect } from 'react'; import { areChartPropsEqual } from './utils/chartUtils'; @@ -17,21 +17,20 @@ function __generateDefaultConfig( containLabel: true }, tooltip: { - padding: [theme.spacing(0.5), theme.spacing(1)], + padding: [theme.spacingValue * 0.5, theme.spacingValue], textStyle: { ...theme.typography.caption, - fontSize: 12, - lineHeight: 16, + fontSize: 11, color: theme.palette.common.white }, - backgroundColor: theme.palette.other.tooltip, + backgroundColor: theme.palette.black[90], ...(tooltipFormatter ? { formatter: tooltipFormatter } : {}) }, color: [theme.palette.secondary.main], xAxis: { axisLabel: { - ...theme.typography.charts, - padding: [theme.spacing(0.5), 0, 0, 0], + ...theme.typography.overlineDelicate, + padding: [theme.spacingValue * 0.5, 0, 0, 0], formatter: (v) => { const formatted = xAxisFormatter(v); return typeof formatted === 'object' @@ -42,7 +41,7 @@ function __generateDefaultConfig( }, yAxis: { axisLabel: { - ...theme.typography.charts, + ...theme.typography.overlineDelicate, formatter: (v) => { const formatted = yAxisFormatter(v); return typeof formatted === 'object' diff --git a/packages/react-ui/src/widgets/TableWidgetUI/TableWidgetUI.js b/packages/react-ui/src/widgets/TableWidgetUI/TableWidgetUI.js index 3719abe79..a3ad1c858 100644 --- a/packages/react-ui/src/widgets/TableWidgetUI/TableWidgetUI.js +++ b/packages/react-ui/src/widgets/TableWidgetUI/TableWidgetUI.js @@ -7,10 +7,11 @@ import { TableContainer, TableHead, TableRow, - makeStyles, TableSortLabel, TablePagination -} from '@material-ui/core'; +} from '@mui/material'; + +import makeStyles from '@mui/styles/makeStyles'; const useStyles = makeStyles((theme) => ({ tableHeadCellLabel: { @@ -55,7 +56,6 @@ function TableWidgetUI({ height, dense }) { - const classes = useStyles(); const paginationRef = useRef(null); const handleSort = (sortField) => { @@ -96,7 +96,6 @@ function TableWidgetUI({ {pagination && ( )} {showClearButton && ( - + Clear )} diff --git a/packages/react-ui/src/widgets/TimeSeriesWidgetUI/components/TimeSeriesChart.js b/packages/react-ui/src/widgets/TimeSeriesWidgetUI/components/TimeSeriesChart.js index 69d4d52f4..328e287b0 100644 --- a/packages/react-ui/src/widgets/TimeSeriesWidgetUI/components/TimeSeriesChart.js +++ b/packages/react-ui/src/widgets/TimeSeriesWidgetUI/components/TimeSeriesChart.js @@ -1,4 +1,4 @@ -import { useTheme } from '@material-ui/core'; +import { useTheme } from '@mui/material'; import React, { useMemo, useState } from 'react'; import ReactEcharts from '../../../custom-components/echarts-for-react'; import useTimeSeriesInteractivity from '../hooks/useTimeSeriesInteractivity'; @@ -38,15 +38,14 @@ export default function TimeSeriesChart({ () => ({ show: tooltip, trigger: 'axis', - padding: [theme.spacing(0.5), theme.spacing(1)], + padding: [theme.spacingValue * 0.5, theme.spacingValue], textStyle: { ...theme.typography.caption, - fontSize: 12, - lineHeight: 16, + fontSize: 11, color: theme.palette.common.white }, borderWidth: 0, - backgroundColor: theme.palette.other.tooltip, + backgroundColor: theme.palette.black[90], position: (point, params, dom, rect, size) => { const position = { top: 0 }; @@ -66,7 +65,7 @@ export default function TimeSeriesChart({ () => ({ axisPointer: { lineStyle: { - color: theme.palette.charts.axisPointer + color: theme.palette.black[40] } }, xAxis: { @@ -88,7 +87,7 @@ export default function TimeSeriesChart({ axisLabel: { margin: 0, verticalAlign: 'bottom', - padding: [0, 0, theme.typography.charts.fontSize, 0], + padding: [0, 0, theme.spacingValue * 1.25, 0], show: true, showMaxLabel: true, showMinLabel: false, @@ -97,13 +96,13 @@ export default function TimeSeriesChart({ // FIXME: Workaround to show only maxlabel let col = 'transparent'; if (value >= maxValue) { - col = theme.palette.charts.maxLabel; + col = theme.palette.black[60]; } return col; }, ...(formatter ? { formatter: (v) => formatter(v) } : {}), - ...theme.typography.charts + ...theme.typography.overlineDelicate }, axisLine: { show: false @@ -115,7 +114,7 @@ export default function TimeSeriesChart({ show: true, onZero: false, lineStyle: { - color: theme.palette.charts.axisLine + color: theme.palette.black[4] } }, max: maxValue @@ -156,10 +155,10 @@ export default function TimeSeriesChart({ const options = useMemo( () => ({ grid: { - left: theme.spacing(2), - top: theme.spacing(4), - right: theme.spacing(2), - bottom: theme.spacing(3) + left: theme.spacingValue * 2, + top: theme.spacingValue * 4, + right: theme.spacingValue * 2, + bottom: theme.spacingValue * 3 }, color: [theme.palette.secondary.main], tooltip: tooltipOptions, diff --git a/packages/react-ui/src/widgets/TimeSeriesWidgetUI/hooks/useTimeSeriesInteractivity.js b/packages/react-ui/src/widgets/TimeSeriesWidgetUI/hooks/useTimeSeriesInteractivity.js index d31346a6c..f2a101f53 100644 --- a/packages/react-ui/src/widgets/TimeSeriesWidgetUI/hooks/useTimeSeriesInteractivity.js +++ b/packages/react-ui/src/widgets/TimeSeriesWidgetUI/hooks/useTimeSeriesInteractivity.js @@ -1,4 +1,4 @@ -import { useTheme } from '@material-ui/core'; +import { useTheme } from '@mui/material'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTimeSeriesContext } from './TimeSeriesContext'; diff --git a/packages/react-ui/src/widgets/WrapperWidgetUI.js b/packages/react-ui/src/widgets/WrapperWidgetUI.js index a6b068eb0..a0bee4999 100644 --- a/packages/react-ui/src/widgets/WrapperWidgetUI.js +++ b/packages/react-ui/src/widgets/WrapperWidgetUI.js @@ -1,6 +1,6 @@ import React, { useState, createRef } from 'react'; import PropTypes from 'prop-types'; -import { makeStyles } from '@material-ui/core/styles'; +import makeStyles from '@mui/styles/makeStyles'; import { Box, Button, @@ -11,10 +11,10 @@ import { LinearProgress, Menu, MenuItem, - Tooltip, - Typography -} from '@material-ui/core'; -import { ExpandLess, ExpandMore, MoreVert } from '@material-ui/icons'; + Tooltip +} from '@mui/material'; +import { ExpandLess, ExpandMore, MoreVert } from '@mui/icons-material'; +import Typography from '../components/atoms/Typography'; /* Options props must have this format: @@ -63,12 +63,10 @@ const useStyles = makeStyles((theme) => ({ alignItems: 'flex-start', justifyContent: 'flex-start', cursor: (props) => (props.expandable ? 'pointer' : 'default'), - '& .MuiButton-label': { - ...theme.typography.body1, - '& .MuiButton-startIcon': { - marginRight: theme.spacing(1) - } + '& .MuiButton-startIcon': { + marginTop: '3px', + marginRight: theme.spacing(1) }, '&:hover': { background: 'none' @@ -186,7 +184,7 @@ function WrapperWidgetUI(props) { } onClick={handleExpandClick} > - + {props.title} @@ -210,7 +208,7 @@ function WrapperWidgetUI(props) { })} {options.length > 0 && ( -
+ <> ))} -
+ )} diff --git a/packages/react-ui/src/widgets/comparative/ComparativeCategoryWidgetUI/CategoryItem.js b/packages/react-ui/src/widgets/comparative/ComparativeCategoryWidgetUI/CategoryItem.js index be73d660c..376d36702 100644 --- a/packages/react-ui/src/widgets/comparative/ComparativeCategoryWidgetUI/CategoryItem.js +++ b/packages/react-ui/src/widgets/comparative/ComparativeCategoryWidgetUI/CategoryItem.js @@ -1,12 +1,5 @@ -import { - Box, - Checkbox, - darken, - Tooltip, - Typography, - useTheme, - withStyles -} from '@material-ui/core'; +import { Box, Checkbox, darken, Tooltip, Typography, useTheme } from '@mui/material'; +import withStyles from '@mui/styles/withStyles'; import React from 'react'; import PropTypes from 'prop-types'; import AnimatedNumber, { @@ -27,7 +20,7 @@ function ComparativeCategoryTooltip({ item, index, names, formatter = IDENTITY_F const name = names[index]; const compareValue = ((data.value - reference.value) / (reference.value || 1)) * 100; - const signText = Math.sign(compareValue) === -1 ? '-' : '+' + const signText = Math.sign(compareValue) === -1 ? '-' : '+'; const valueColor = Math.sign(compareValue) === -1 ? theme.palette.error.main @@ -42,9 +35,11 @@ function ComparativeCategoryTooltip({ item, index, names, formatter = IDENTITY_F
{name} - - + + - {signText}{formatter(Math.abs(compareValue))} + {signText} + {formatter(Math.abs(compareValue))} @@ -78,7 +86,7 @@ ComparativeCategoryTooltip.propTypes = { item: transposedCategoryItemPropTypes, names: PropTypes.arrayOf(PropTypes.string).isRequired, formatter: PropTypes.func, - index: PropTypes.number, + index: PropTypes.number }; const StyledTooltip = withStyles((theme) => ({ @@ -111,21 +119,35 @@ function CategoryItem({ } const tooltipContent = (index) => ( - + ); return ( onClick(item.key)} className={className} > {showCheckbox ? : null} - + {item.label} @@ -136,10 +158,11 @@ function CategoryItem({ placement='top-start' >
v; const EMPTY_ARRAY = []; @@ -36,7 +36,7 @@ function SearchIcon() { } /** Renders a `` widget - * + * *