Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New DS core components: Menu & MenuItem #870

Merged
merged 20 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Not released

- New DS core components: Menu & MenuItem [#828](https://github.com/CartoDB/carto-react/pull/828)

## 3.0.0

### 3.0.0-alpha.8 (2024-04-23)
Expand All @@ -12,7 +14,7 @@

### 3.0.0-alpha.7 (2024-04-16)

- Update Deck GL v9 and removed dropping features functionality [#838](https://github.com/CartoDB/carto-react/pull/838)
- Update Deck GL v9 and removed dropping features functionality [#838](https://github.com/CartoDB/carto-react/pull/838)

## 2.5

Expand All @@ -22,8 +24,8 @@

### 2.5.2 (2024-04-10)

- configurable timeseries y axis [#861](https://github.com/CartoDB/carto-react/pull/861)
- configurable histogram y axis [#860](https://github.com/CartoDB/carto-react/pull/860)
- configurable timeseries y axis [#861](https://github.com/CartoDB/carto-react/pull/861)
- configurable histogram y axis [#860](https://github.com/CartoDB/carto-react/pull/860)

### 2.5.1 (2024-04-03)

Expand Down
3 changes: 3 additions & 0 deletions packages/react-ui/src/components/atoms/SelectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const StyledSelect = styled(Select)(({ theme }) => ({
},
'& .MuiSelect-select .MuiMenuItem-root:hover': {
backgroundColor: 'transparent'
},
'&.MuiInputBase-root .MuiSelect-select.MuiSelect-multiple': {
paddingRight: theme.spacing(7)
Copy link
Contributor

@VictorVelarde VictorVelarde May 24, 2024

Choose a reason for hiding this comment

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

Looks (potentially) a bit risky for current use cases... isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It applies only to multiple selection case, and we need it globally for this use case 👍

}
}));

Expand Down
10 changes: 10 additions & 0 deletions packages/react-ui/src/components/molecules/Menu.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MenuProps as MuiMenuProps } from '@mui/material';

export type MenuProps = MuiMenuProps & {
extended?: boolean;
width?: string;
height?: string;
};

declare const Menu: (props: MenuProps) => JSX.Element;
export default Menu;
38 changes: 38 additions & 0 deletions packages/react-ui/src/components/molecules/Menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Menu as MuiMenu, styled } from '@mui/material';

const StyledMenu = styled(MuiMenu, {
shouldForwardProp: (prop) => !['extended', 'width', 'height'].includes(prop)
})(({ extended, width, height, theme }) => ({
...(extended && {
'.MuiMenuItem-root': {
minHeight: theme.spacing(6)
}
}),
'.MuiMenu-paper': {
...(width && {
width: width,
minWidth: width
}),
...(height && {
maxHeight: height
})
}
}));

const Menu = ({ extended, width, height, children, ...otherProps }) => {
return (
<StyledMenu extended={extended} width={width} height={height} {...otherProps}>
{children}
</StyledMenu>
);
};

Menu.propTypes = {
extended: PropTypes.bool,
width: PropTypes.string,
height: PropTypes.string
};

export default Menu;
10 changes: 10 additions & 0 deletions packages/react-ui/src/components/molecules/MenuItem.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MenuItemProps as MuiMenuItemProps } from '@mui/material';

export type MenuItemProps = MuiMenuItemProps & {
subtitle?: boolean;
destructive?: boolean;
extended?: boolean;
};

declare const MenuItem: (props: MenuItemProps) => JSX.Element;
export default MenuItem;
89 changes: 89 additions & 0 deletions packages/react-ui/src/components/molecules/MenuItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MenuItem as MuiMenuItem, styled } from '@mui/material';

const StyledMenuItem = styled(MuiMenuItem, {
shouldForwardProp: (prop) => !['subtitle', 'destructive', 'extended'].includes(prop)
})(({ subtitle, destructive, extended, theme }) => ({
...(subtitle && {
pointerEvents: 'none',
columnGap: 0,
...theme.typography.caption,
fontWeight: 500,
color: theme.palette.text.secondary,

'&.MuiMenuItem-root': {
minHeight: theme.spacing(3),
paddingTop: 0,
paddingBottom: 0,

'&:not(:first-of-type)': {
minHeight: theme.spacing(5),
marginTop: theme.spacing(1),
paddingTop: theme.spacing(1),
borderTop: `1px solid ${theme.palette.divider}`
}
}
}),
...(destructive && {
color: theme.palette.error.main,

'.MuiTypography-root': {
color: theme.palette.error.main
},
'svg, & .MuiSvgIcon-root': {
color: theme.palette.error.main
},

'&:hover': {
backgroundColor: theme.palette.error.relatedLight
},
'&.Mui-selected': {
color: theme.palette.error.main,

'.MuiTypography-root': {
color: theme.palette.error.main
},
'svg, & .MuiSvgIcon-root': {
color: theme.palette.error.main
}
},

'&.Mui-disabled': {
color: theme.palette.text.disabled,

'.MuiTypography-root': {
color: theme.palette.text.disabled
},
svg: {
color: theme.palette.text.disabled
}
}
}),
...(extended && {
'&.MuiMenuItem-root': {
minHeight: theme.spacing(6)
}
})
}));

const MenuItem = ({ subtitle, destructive, extended, children, ...otherProps }) => {
return (
<StyledMenuItem
subtitle={subtitle}
destructive={destructive}
extended={extended}
{...otherProps}
>
{children}
</StyledMenuItem>
);
};

MenuItem.propTypes = {
subtitle: PropTypes.bool,
destructive: PropTypes.bool,
extended: PropTypes.bool
};

export default MenuItem;
Original file line number Diff line number Diff line change
@@ -1,59 +1,54 @@
import React from 'react';
import { useIntl } from 'react-intl';
import useImperativeIntl from '../../../hooks/useImperativeIntl';
import { Box, Link, styled } from '@mui/material';
import Typography from '../../atoms/Typography';
import { Box, Checkbox, Link, styled } from '@mui/material';

const FiltersRoot = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5),
marginLeft: 'auto',
pointerEvents: 'auto'
position: 'sticky',
top: 0,
left: 0,
bottom: 0,
right: 0,
zIndex: 2,
marginBottom: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
borderBottom: `1px solid ${theme.palette.divider}`
}));

const LinkFilter = styled(Link)(({ disabled, theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
width: '100%',
height: theme.spacing(6),
padding: theme.spacing(0.5, 1.5),
textAlign: 'initial',

...(disabled && { pointerEvents: 'none', color: theme.palette.text.disabled })
}));

function Filters({
showFilters,
areAllSelected,
areAnySelected,
selectAll,
unselectAll,
selectAllDisabled
}) {
function Filters({ areAllSelected, areAnySelected, selectAll, selectAllDisabled }) {
const intl = useIntl();
const intlConfig = useImperativeIntl(intl);

if (!showFilters) {
return null;
}

return (
<FiltersRoot>
<LinkFilter
variant='caption'
variant='body2'
color='textPrimary'
component='button'
disabled={areAllSelected || selectAllDisabled}
underline='none'
disabled={selectAllDisabled}
onClick={selectAll}
tabIndex={0}
>
<Checkbox
checked={areAllSelected}
indeterminate={areAnySelected && !areAllSelected}
disabled={selectAllDisabled}
/>
{intlConfig.formatMessage({ id: 'c4r.form.selectAll' })}
</LinkFilter>
<Typography variant='caption' weight='strong' color='text.hint'>
</Typography>
<LinkFilter
variant='caption'
component='button'
onClick={unselectAll}
disabled={!areAnySelected}
>
{intlConfig.formatMessage({
id: 'c4r.form.selectNone'
})}
</LinkFilter>
</FiltersRoot>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ import React, { forwardRef, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import useImperativeIntl from '../../../hooks/useImperativeIntl';
import { Checkbox, ListItemText, MenuItem, Tooltip, styled } from '@mui/material';
import {
Checkbox,
IconButton,
InputAdornment,
ListItemText,
MenuItem,
Tooltip,
styled
} from '@mui/material';
import { Cancel } from '@mui/icons-material';

import SelectField from '../../atoms/SelectField';
import Typography from '../../atoms/Typography';
Expand All @@ -20,6 +29,20 @@ const StyledMenuItem = styled(MenuItem)(({ theme }) => ({
}
}));

const UnselectButton = styled(InputAdornment)(({ theme }) => ({
display: 'none',
position: 'absolute',
top: theme.spacing(2),
right: theme.spacing(3.5),

'.MuiInputBase-root:hover &, .MuiInputBase-root.Mui-focused &': {
display: 'flex'
},
'.MuiSvgIcon-root': {
color: theme.palette.text.hint
}
}));

const MultipleSelectField = forwardRef(
(
{
Expand Down Expand Up @@ -121,23 +144,41 @@ const MultipleSelectField = forwardRef(
renderValue={() => renderValue}
onChange={handleChange}
size={size}
labelSecondary={
endAdornment={
showFilters &&
areAnySelected && (
<UnselectButton position='end'>
<IconButton onClick={unselectAll} size='small'>
<Cancel />
</IconButton>
</UnselectButton>
)
}
menuProps={{
PaperProps: {
sx: {
'.MuiList-root': {
paddingTop: 0
}
}
}
}}
>
{showFilters && (
<Filters
showFilters={showFilters}
areAllSelected={areAllSelected}
areAnySelected={areAnySelected}
selectAll={selectAll}
unselectAll={unselectAll}
selectAllDisabled={selectAllDisabled}
/>
}
>
)}
{options?.map((option) => {
const item = (
<StyledMenuItem
key={option.value}
value={option.value}
disabled={option.disabled}
tabIndex={0}
>
<Checkbox
disabled={option.disabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,16 @@ export default function useMultipleSelectField({ selectedOptions, options, onCha
.map(({ value }) => value);

if (optionsValues) {
setCurrentOptions(optionsValues);
onChange(optionsValues);
const allSelected = optionsValues.every((value) => currentOptions.includes(value));
if (allSelected) {
// Deselect all options
setCurrentOptions([]);
onChange([]);
} else {
// Select all options
setCurrentOptions(optionsValues);
onChange(optionsValues);
}
}
};

Expand Down
Loading
Loading