diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f628306..f205abcff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Not released +- ToggleButtonGroup component: Add support for variant and backgroundColor [#824](https://github.com/CartoDB/carto-react/pull/824) + ## 2.3 ### 2.3.7 (2024-01-11) diff --git a/UPGRADE.md b/UPGRADE.md index fe7612a5c..8cfa7c3bc 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -204,6 +204,18 @@ So, instead of Mui Button, the component you should use to create buttons is thi For external use: `import { Button } from '@carto/react-ui';`. +### ToggleButtonGroup + +We have a `ToggleButtonGroup` component that uses `Mui ToggleButtonGroup` and extends it with some extra props: + +- variant +- backgroundColor + +So, instead of Mui ToggleButtonGroup, the component you should use is this one: +`react-ui/src/components/atoms/ToggleButtonGroup` + +For external use: `import { ToggleButtonGroup } from '@carto/react-ui';`. + ### AppBar We have a custom component to build the basic structure and styles on top of AppBar Mui component. diff --git a/packages/react-ui/src/components/atoms/ToggleButtonGroup.d.ts b/packages/react-ui/src/components/atoms/ToggleButtonGroup.d.ts new file mode 100644 index 000000000..b292f506d --- /dev/null +++ b/packages/react-ui/src/components/atoms/ToggleButtonGroup.d.ts @@ -0,0 +1,9 @@ +import { ToggleButtonGroupProps as MuiToggleButtonGroupProps } from '@mui/material'; + +export type ToggleButtonGroupProps = MuiToggleButtonGroupProps & { + variant?: 'contained' | 'floating' | 'unbounded'; + backgroundColor?: 'primary' | 'secondary' | 'transparent'; +}; + +declare const ToggleButtonGroup: (props: ToggleButtonGroupProps) => JSX.Element; +export default ToggleButtonGroup; diff --git a/packages/react-ui/src/components/atoms/ToggleButtonGroup.js b/packages/react-ui/src/components/atoms/ToggleButtonGroup.js new file mode 100644 index 000000000..3e84bbd79 --- /dev/null +++ b/packages/react-ui/src/components/atoms/ToggleButtonGroup.js @@ -0,0 +1,110 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ToggleButtonGroup as MuiToggleButtonGroup, styled } from '@mui/material'; + +const StyledToggleButtonGroup = styled(MuiToggleButtonGroup, { + shouldForwardProp: (prop) => !['variant', 'backgroundColor'].includes(prop) +})(({ variant, backgroundColor, theme }) => ({ + // Variants + ...(variant === 'contained' && { + boxShadow: 'none' + }), + ...(variant === 'unbounded' && { + boxShadow: 'none', + borderRadius: theme.spacing(0.5), + + '& .MuiDivider-root': { + height: theme.spacing(4), + + '&.MuiToggleButtonGroup-groupedHorizontal': { + height: theme.spacing(4) + }, + '&.MuiToggleButtonGroup-groupedVertical': { + height: 'auto', + width: theme.spacing(4), + margin: `${theme.spacing(0.5, 0, 1)} !important`, + borderRadius: '0 !important' + } + }, + + '& .MuiToggleButton-sizeSmall': { + margin: 0, + + '&.MuiToggleButtonGroup-grouped:not(.MuiDivider-root)': { + margin: 0 + }, + '& + .MuiDivider-root.MuiToggleButtonGroup-groupedHorizontal': { + height: theme.spacing(3) + }, + '& + .MuiDivider-root.MuiToggleButtonGroup-groupedVertical': { + height: 'auto', + width: theme.spacing(3) + } + }, + + '.MuiToggleButtonGroup-grouped:not(.MuiDivider-root)': { + margin: 0, + + '&:first-of-type': { + marginLeft: 0 + }, + '&:not(:last-of-type)': { + marginRight: theme.spacing(0.5) + } + }, + '&.MuiToggleButtonGroup-horizontal:not(.MuiDivider-root)': { + '.MuiToggleButtonGroup-grouped': { + margin: theme.spacing(0, 0.5) + } + }, + '&.MuiToggleButtonGroup-vertical:not(.MuiDivider-root)': { + '.MuiToggleButtonGroup-grouped': { + margin: theme.spacing(0, 0, 0.5), + + '&:not(:last-of-type)': { + marginRight: 0 + }, + '&:last-of-type': { + marginBottom: 0 + } + } + } + }), + + // Colors + ...(backgroundColor === 'primary' && { + backgroundColor: theme.palette.background.paper + }), + ...(backgroundColor === 'secondary' && { + backgroundColor: theme.palette.background.default + }), + ...(backgroundColor === 'transparent' && { + backgroundColor: 'transparent' + }) +})); + +const ToggleButtonGroup = ({ children, variant, backgroundColor, ...rest }) => { + const isUnbounded = variant === 'unbounded'; + const defaultColor = isUnbounded ? 'transparent' : 'primary'; + + return ( + + {children} + + ); +}; + +ToggleButtonGroup.defaultProps = { + variant: 'floating' +}; + +ToggleButtonGroup.propTypes = { + variant: PropTypes.oneOf(['floating', 'contained', 'unbounded']), + backgroundColor: PropTypes.oneOf(['primary', 'secondary', 'transparent']) +}; + +export default ToggleButtonGroup; diff --git a/packages/react-ui/src/index.d.ts b/packages/react-ui/src/index.d.ts index 7f2494631..3899b5a77 100644 --- a/packages/react-ui/src/index.d.ts +++ b/packages/react-ui/src/index.d.ts @@ -37,8 +37,11 @@ import Typography, { TypographyProps } from './components/atoms/Typography'; import Button, { ButtonProps } from './components/atoms/Button'; -import PasswordField, { PasswordFieldProps } from './components/atoms/PasswordField'; import SelectField, { SelectFieldProps } from './components/atoms/SelectField'; +import PasswordField, { PasswordFieldProps } from './components/atoms/PasswordField'; +import ToggleButtonGroup, { + ToggleButtonGroupProps +} from './components/atoms/ToggleButtonGroup'; import UploadField, { UploadFieldProps } from './components/molecules/UploadField/UploadField'; @@ -101,6 +104,8 @@ export { CartoFontWeight, Button, ButtonProps, + ToggleButtonGroup, + ToggleButtonGroupProps, PasswordField, PasswordFieldProps, SelectField, diff --git a/packages/react-ui/src/index.js b/packages/react-ui/src/index.js index a9c7165f8..19960221f 100644 --- a/packages/react-ui/src/index.js +++ b/packages/react-ui/src/index.js @@ -31,6 +31,7 @@ import CircleIcon from './assets/icons/CircleIcon'; import ArrowDropIcon from './assets/icons/ArrowDropIcon'; import Typography from './components/atoms/Typography'; import Button from './components/atoms/Button'; +import ToggleButtonGroup from './components/atoms/ToggleButtonGroup'; import PasswordField from './components/atoms/PasswordField'; import SelectField from './components/atoms/SelectField'; import UploadField from './components/molecules/UploadField/UploadField'; @@ -86,6 +87,7 @@ export { LegendRamp, Typography, Button, + ToggleButtonGroup, PasswordField, SelectField, UploadField, diff --git a/packages/react-ui/src/theme/sections/components/buttons.js b/packages/react-ui/src/theme/sections/components/buttons.js index 6cfc974a9..06c484452 100644 --- a/packages/react-ui/src/theme/sections/components/buttons.js +++ b/packages/react-ui/src/theme/sections/components/buttons.js @@ -397,9 +397,25 @@ export const buttonsOverrides = { borderRadius: radius }, '.MuiDivider-root': { - height: sizeLarge, - margin: theme.spacing(0, 1), - marginLeft: theme.spacing(0.5) + '&.MuiToggleButtonGroup-groupedHorizontal': { + height: sizeLarge, + margin: theme.spacing(0, 1), + marginLeft: theme.spacing(0.5) + }, + '&.MuiToggleButtonGroup-groupedVertical': { + width: sizeLarge, + margin: theme.spacing(1, 0), + marginTop: theme.spacing(0.5) + } + }, + + '.MuiToggleButton-sizeSmall': { + '& + .MuiDivider-root.MuiToggleButtonGroup-groupedHorizontal': { + height: sizeMedium + }, + '& + .MuiDivider-root.MuiToggleButtonGroup-groupedVertical': { + width: sizeMedium + } } }), // Styles applied to the children if orientation="horizontal" @@ -412,18 +428,15 @@ export const buttonsOverrides = { marginLeft: 0, borderLeft: 'none' }, - '&:first-of-type': { + '&:first-of-type:not(.MuiDivider-root)': { marginLeft: theme.spacing(1) }, - '&.MuiToggleButton-sizeSmall': { + '&.MuiToggleButton-sizeSmall:not(.MuiDivider-root)': { height: sizeSmall, margin: theme.spacing(0.5), '&:not(:first-of-type)': { marginLeft: 0 - }, - '& + .MuiDivider-root': { - height: sizeMedium } } }), @@ -434,7 +447,11 @@ export const buttonsOverrides = { '&.MuiToggleButton-root': { marginLeft: theme.spacing(1), - marginBottom: theme.spacing(0.5) + marginBottom: theme.spacing(0.5), + + '&:last-of-type': { + marginBottom: theme.spacing(1) + } }, '&.MuiToggleButton-sizeSmall': { width: sizeSmall, @@ -442,6 +459,9 @@ export const buttonsOverrides = { '&:not(:first-of-type)': { marginTop: 0 + }, + '&:last-of-type': { + marginBottom: theme.spacing(0.5) } } }) diff --git a/packages/react-ui/storybook/stories/molecules/ToggleButton.stories.js b/packages/react-ui/storybook/stories/molecules/ToggleButton.stories.js index 9de133081..526fb8fce 100644 --- a/packages/react-ui/storybook/stories/molecules/ToggleButton.stories.js +++ b/packages/react-ui/storybook/stories/molecules/ToggleButton.stories.js @@ -1,5 +1,5 @@ import React from 'react'; -import { ToggleButtonGroup, ToggleButton, Grid, Divider } from '@mui/material'; +import { ToggleButton, Grid, Divider } from '@mui/material'; import { CheckCircleOutline, FormatAlignCenter, @@ -8,11 +8,26 @@ import { FormatAlignRight } from '@mui/icons-material'; import Typography from '../../../src/components/atoms/Typography'; +import ToggleButtonGroup from '../../../src/components/atoms/ToggleButtonGroup'; +import { DocContainer, DocHighlight, DocLink } from '../../utils/storyStyles'; const options = { title: 'Molecules/Toggle Button', component: ToggleButtonGroup, argTypes: { + variant: { + defaultValue: 'floating', + control: { + type: 'select', + options: ['floating', 'contained', 'unbounded'] + } + }, + backgroundColor: { + control: { + type: 'select', + options: ['primary', 'secondary', 'transparent'] + } + }, size: { defaultValue: 'medium', control: { @@ -57,13 +72,13 @@ const options = { url: 'https://www.figma.com/file/nmaoLeo69xBJCHm9nc6lEV/CARTO-Components-1.0?node-id=1534%3A36258' }, status: { - type: 'validated' + type: 'readyToReview' } } }; export default options; -const Toggle = ({ label, exclusive, ...rest }) => { +const Toggle = ({ label, ...rest }) => { const [selected, setSelected] = React.useState(false); return ( @@ -81,8 +96,10 @@ const Toggle = ({ label, exclusive, ...rest }) => { ); }; -const ToggleRow = ({ label, divider, fullWidth, exclusive, ...rest }) => { +const ToggleRow = ({ label, divider, fullWidth, exclusive, orientation, ...rest }) => { const [selected, setSelected] = React.useState(() => ['AlignLeft']); + const isVertical = orientation === 'vertical'; + const dividerOrientation = isVertical ? 'horizontal' : 'vertical'; const handleAlignment = (event, newAlignment) => { setSelected(newAlignment); @@ -95,6 +112,7 @@ const ToggleRow = ({ label, divider, fullWidth, exclusive, ...rest }) => { onChange={handleAlignment} fullWidth={fullWidth} exclusive={exclusive} + orientation={orientation} aria-label='text alignment' > @@ -103,7 +121,9 @@ const ToggleRow = ({ label, divider, fullWidth, exclusive, ...rest }) => { {label ? label : } - {divider && } + {divider && ( + + )} {label ? label : } @@ -114,7 +134,33 @@ const ToggleRow = ({ label, divider, fullWidth, exclusive, ...rest }) => { ); }; -const IconTemplate = ({ exclusive, ...args }) => { +const DocTemplate = () => { + return ( + + We have our own + + ToggleButtonGroup + + component that extends Mui ToggleButtonGroup with some props (variant and + backgroundColor support). + + So, instead of Mui ToggleButtonGroup, you should use this one: + + react-ui/src/components/atoms/ToggleButtonGroup + + + + For external use: + + {'import { ToggleButtonGroup } from "@carto/react-ui";'} + + . + + + ); +}; + +const IconTemplate = (args) => { return ( @@ -130,7 +176,7 @@ const IconTemplate = ({ exclusive, ...args }) => { ); }; -const TextTemplate = ({ exclusive, ...args }) => { +const TextTemplate = (args) => { return ( @@ -143,7 +189,7 @@ const TextTemplate = ({ exclusive, ...args }) => { ); }; -const GroupTemplate = ({ exclusive, ...args }) => { +const GroupTemplate = (args) => { return ( @@ -156,7 +202,7 @@ const GroupTemplate = ({ exclusive, ...args }) => { ); }; -const VerticalGroupTemplate = ({ exclusive, ...args }) => { +const VerticalGroupTemplate = (args) => { return ( @@ -169,7 +215,7 @@ const VerticalGroupTemplate = ({ exclusive, ...args }) => { ); }; -const DividedTemplate = ({ exclusive, ...args }) => { +const DividedTemplate = (args) => { return ( @@ -182,6 +228,38 @@ const DividedTemplate = ({ exclusive, ...args }) => { ); }; +const VariantTemplate = (args) => { + return ( + + + + + + + + + + + + ); +}; + +const BgColorTemplate = (args) => { + return ( + + + + + + + + + + + + ); +}; + const BehaviorTemplate = ({ exclusive, ...args }) => { const [selected, setSelected] = React.useState(() => ['opt1']); const [selected2, setSelected2] = React.useState(() => ['opt1']); @@ -234,6 +312,8 @@ const BehaviorTemplate = ({ exclusive, ...args }) => { export const Playground = ToggleRow.bind({}); +export const Guide = DocTemplate.bind({}); + export const Icon = IconTemplate.bind({}); export const Text = TextTemplate.bind({}); @@ -251,4 +331,8 @@ HorizontalTextGroup.args = { label: 'Text' }; export const MultipleSelectionGroup = GroupTemplate.bind({}); MultipleSelectionGroup.args = { exclusive: false }; +export const Variant = VariantTemplate.bind({}); + +export const BackgroundColor = BgColorTemplate.bind({}); + export const Behavior = BehaviorTemplate.bind({});