Skip to content

Commit

Permalink
FeatureSelection widget fixes (#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmilan authored Jun 15, 2023
1 parent 33b4ba3 commit 4d7e9e8
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

## Not released

- FeatureSelection widget fixes [#708](https://github.com/CartoDB/carto-react/pull/708)

## 2.0

### 2.0.9 (2023-06-14)
Expand Down
4 changes: 4 additions & 0 deletions packages/react-ui/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ export type FeatureSelectionWidgetUI = {
onSelectGeometry?: Function;
onDeleteGeometry?: Function;
tooltipPlacement?: 'bottom' | 'left' | 'right' | 'top';
size?: 'small' | 'medium';
chipLabel?: string;
};

export type FeatureSelectionUIDropdown = {
Expand All @@ -178,6 +180,7 @@ export type FeatureSelectionUIDropdown = {
enabled?: boolean;
onEnabledChange?: Function;
tooltipPlacement?: 'bottom' | 'left' | 'right' | 'top';
editDisabled?: boolean;
};
export type FeatureSelectionUIGeometryChips = {
features: GeoJSON.Feature[];
Expand All @@ -187,6 +190,7 @@ export type FeatureSelectionUIGeometryChips = {
disabledChipTooltip?: string;
size?: 'small' | 'medium';
tooltipPlacement?: 'bottom' | 'left' | 'right' | 'top';
chipLabel?: string;
};
export type FeatureSelectionUIToggleButton = {
icon: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArrowDropDown } from '@mui/icons-material';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import {
Box,
Divider,
Expand All @@ -14,11 +14,29 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Typography from '../../components/atoms/Typography';

const StyledButtonArrow = styled(IconButton)(({ theme: { spacing, palette } }) => ({
color: palette.text.secondary,
width: spacing(3)
const ArrowButton = styled(IconButton, {
shouldForwardProp: (prop) => prop !== 'isOpen'
})(({ isOpen, theme }) => ({
color: theme.palette.text.secondary,
width: theme.spacing(3),
transform: `rotate(${isOpen ? '180' : '0'}deg)`,
backgroundColor: isOpen ? theme.palette.action.hover : undefined
}));

const SelectionMenuItem = styled(MenuItem, {
shouldForwardProp: (prop) => prop !== 'disabled'
})(({ disabled, theme }) => ({
...(disabled && {
pointerEvents: 'none',
color: theme.palette.text.disabled
})
}));

const DisabledMenuItem = styled(MenuItem)(({ theme }) => ({
'&.Mui-disabled': {
opacity: 1
}
}));
/**
* Renders a `<FeatureSelectionUIDropdown />` component.
* This component displays the dropdown layout with all available edit and selection modes
Expand All @@ -39,6 +57,7 @@ const StyledButtonArrow = styled(IconButton)(({ theme: { spacing, palette } }) =
* @param { "bottom" | "left" | "right" | "top" | undefined } [props.tooltipPlacement]
* @param {string} [props.tooltipText]
* @param {string} [props.menuHeaderText]
* @param {boolean} [props.editDisabled]
* -->
*/
function FeatureSelectionUIDropdown({
Expand All @@ -49,7 +68,8 @@ function FeatureSelectionUIDropdown({
enabled,
tooltipPlacement,
tooltipText = '',
menuHeaderText = ''
menuHeaderText = '',
editDisabled
}) {
const theme = useTheme();
const [anchorEl, setAnchorEl] = useState(null);
Expand All @@ -72,34 +92,39 @@ function FeatureSelectionUIDropdown({
};

const showDivider = !!selectionModes.length && !!editModes.length;
const isEditItem = (mode) => editModes.find((editMode) => editMode.id === mode.id);

const createMenuItemWrapper = (mode) => (
<MenuItem
<SelectionMenuItem
key={mode.id}
selected={enabled && selectedMode === mode.id}
onClick={() => handleSelectMode(mode.id)}
disabled={editDisabled && isEditItem(mode)}
>
<Box display='flex' justifyContent='space-between' alignItems='center'>
{mode.icon}
<Box ml={2}>
<Typography variant='body2'>{capitalize(mode.label)}</Typography>
<Typography variant='body2' color='inherit'>
{capitalize(mode.label)}
</Typography>
</Box>
</Box>
</MenuItem>
</SelectionMenuItem>
);

return (
<>
<Tooltip title={tooltipText} placement={tooltipPlacement}>
<StyledButtonArrow
<ArrowButton
id='feature-selection-menu-button'
aria-controls='feature-selection-menu'
aria-haspopup='true'
aria-expanded={open ? 'true' : undefined}
onClick={openDropdown}
isOpen={open}
>
<ArrowDropDown />
</StyledButtonArrow>
<KeyboardArrowDown />
</ArrowButton>
</Tooltip>
<Menu
id='feature-selection-menu'
Expand All @@ -110,9 +135,11 @@ function FeatureSelectionUIDropdown({
MenuListProps={{ 'aria-labelledby': 'feature-selection-menu-button' }}
>
{menuHeaderText && (
<MenuItem disabled>
<Typography variant='caption'>{menuHeaderText}</Typography>
</MenuItem>
<DisabledMenuItem disabled>
<Typography variant='caption' color='textSecondary'>
{menuHeaderText}
</Typography>
</DisabledMenuItem>
)}
{!!selectionModes.length && selectionModes.map(createMenuItemWrapper)}
{showDivider && <Divider sx={{ margin: ({ spacing }) => spacing(1, 0) }} />}
Expand All @@ -136,7 +163,8 @@ FeatureSelectionUIDropdown.propTypes = {
enabled: PropTypes.bool,
tooltipPlacement: PropTypes.string,
tooltipText: PropTypes.string,
menuHeaderText: PropTypes.string
menuHeaderText: PropTypes.string,
editDisabled: PropTypes.bool
};
FeatureSelectionUIDropdown.defaultProps = {
onSelectMode: () => {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Cancel } from '@mui/icons-material';
import { Box, Chip, List, ListItem, Tooltip, styled } from '@mui/material';
import PropTypes from 'prop-types';
import React from 'react';
import React, { useState } from 'react';

const ChipList = styled(List)(({ theme: { spacing } }) => ({
const ChipList = styled(List)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
margin: 0,
padding: spacing(0, 1),
marginLeft: theme.spacing(1.5),
padding: 0,
overflowX: 'auto',
maxWidth: '100%',
scrollbarWidth: 'none',
msOverflowStyle: 'none',

'&::-webkit-scrollbar': {
display: 'none'
}
Expand All @@ -26,8 +28,9 @@ const NOOP = () => {};
* @param {function} [props.onDeleteGeometry]
* @param {string} [props.chipTooltip]
* @param {string} [props.disabledChipTooltip]
* @param { "medium" | "small" } [props.size]
* @param { "medium" | "small" | undefined } [props.size]
* @param { "bottom" | "left" | "right" | "top" | undefined } [props.tooltipPlacement]
* @param {string} [props.chipLabel]
* @returns
*/
function FeatureSelectionUIGeometryChips({
Expand All @@ -37,7 +40,8 @@ function FeatureSelectionUIGeometryChips({
chipTooltip,
disabledChipTooltip,
size = 'medium',
tooltipPlacement = 'bottom'
tooltipPlacement = 'bottom',
chipLabel
}) {
/**
* @param {GeoJSON.Geometry['type']} type
Expand All @@ -52,19 +56,27 @@ function FeatureSelectionUIGeometryChips({

function getFeatureChipLabel(feature, index) {
const type = translateType(feature.geometry.type);
return feature.properties?.name || `${type} ${index + 1}`;
return chipLabel || feature.properties?.name || `${type} ${index + 1}`;
}

const [showDeleteTooltip, setShowDeleteTooltip] = useState(false);

return (
<Box sx={{ overflowX: 'auto' }}>
<ChipList sx={{ gap: size === 'small' ? 0.5 : 1 }}>
{features.map((geometry, index) => {
const isDisabled = geometry.properties?.disabled;
const tooltipText = isDisabled
? disabledChipTooltip || chipTooltip
: showDeleteTooltip
? 'Remove'
: chipTooltip;

return (
<ListItem disablePadding key={index}>
<Tooltip
disableHoverListener={isDisabled ? !disabledChipTooltip : !chipTooltip}
title={isDisabled ? disabledChipTooltip || chipTooltip : chipTooltip}
title={tooltipText}
placement={tooltipPlacement}
>
<Chip
Expand All @@ -75,6 +87,12 @@ function FeatureSelectionUIGeometryChips({
onDelete={
onDeleteGeometry ? () => onDeleteGeometry(geometry) : undefined
}
deleteIcon={
<Cancel
onMouseEnter={() => setShowDeleteTooltip(true)}
onMouseLeave={() => setShowDeleteTooltip(false)}
/>
}
/>
</Tooltip>
</ListItem>
Expand Down Expand Up @@ -105,7 +123,8 @@ FeatureSelectionUIGeometryChips.propTypes = {
'right-start',
'top-end',
'top-start'
])
]),
chipLabel: PropTypes.string
};
FeatureSelectionUIGeometryChips.defaultProps = {
size: 'medium',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import FeatureSelectionUIToggleButton from './FeatureSelectionUIToggleButton';
import FeatureSelectionUIGeometryChips from './FeatureSelectionUIGeometryChips';
import FeatureSelectionUIDropdown from './FeatureSelectionUIDropdown';

const StylesWrapper = styled(Paper)(({ theme: { spacing, palette, shape } }) => ({
const StylesWrapper = styled(Paper)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: spacing(0.5)
maxHeight: theme.spacing(5),
padding: theme.spacing(0.5)
}));

/**
Expand All @@ -33,6 +34,9 @@ const StylesWrapper = styled(Paper)(({ theme: { spacing, palette, shape } }) =>
* @param {function} [props.onSelectGeometry]
* @param {function} [props.onDeleteGeometry]
* @param { "bottom" | "left" | "right" | "top" | undefined } [props.tooltipPlacement]
* @param { "small" | "medium" | undefined } [props.size]
* @param {string} [props.chipLabel]
*
* -->
*/
function FeatureSelectionWidgetUI({
Expand All @@ -45,7 +49,9 @@ function FeatureSelectionWidgetUI({
geometry,
onSelectGeometry,
onDeleteGeometry,
tooltipPlacement = 'bottom'
tooltipPlacement = 'bottom',
size = 'medium',
chipLabel
}) {
const selectedModeData = useMemo(() => {
const modes = [
Expand Down Expand Up @@ -84,15 +90,18 @@ function FeatureSelectionWidgetUI({
tooltipPlacement={tooltipPlacement}
tooltipText='Select a mode'
menuHeaderText='Choose a selection mode'
editDisabled={!geometry}
/>
{!!geometry && (
<FeatureSelectionUIGeometryChips
features={[geometry]}
onSelectGeometry={onSelectGeometry}
onDeleteGeometry={onDeleteGeometry}
chipTooltip='Apply mask'
disabledChipTooltip='Clear mask'
disabledChipTooltip='Apply mask'
chipTooltip='Clear mask'
tooltipPlacement={tooltipPlacement}
size={size}
chipLabel={chipLabel}
/>
)}
</StylesWrapper>
Expand All @@ -102,7 +111,8 @@ function FeatureSelectionWidgetUI({
FeatureSelectionWidgetUI.defaultProps = {
enabled: false,
tooltipPlacement: 'bottom',
editModes: []
editModes: [],
size: 'medium'
};

const MODE_SHAPE = PropTypes.shape({
Expand All @@ -120,7 +130,9 @@ FeatureSelectionWidgetUI.propTypes = {
onEnabledChange: PropTypes.func,
geometry: PropTypes.any,
onSelectGeometry: PropTypes.func,
tooltipPlacement: PropTypes.string
tooltipPlacement: PropTypes.string,
size: PropTypes.oneOf(['small', 'medium']),
chipLabel: PropTypes.string
};

export default FeatureSelectionWidgetUI;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ const options = {
argTypes: {
enabled: {
control: { type: 'boolean' }
},
size: {
control: {
type: 'select',
options: ['small', 'medium']
}
}
},
parameters: {
Expand Down Expand Up @@ -38,7 +44,7 @@ const Template = (args) => {
const [selectedMode, setSelectedMode] = useState(FEATURE_SELECTION_MODES[0].id);

return (
<Box display='inline-block' minWidth={72} maxWidth={400}>
<Box display='inline-block'>
<FeatureSelectionWidgetUI
selectionModes={FEATURE_SELECTION_MODES}
editModes={EDIT_MODES}
Expand Down Expand Up @@ -84,3 +90,21 @@ const WithGeometryProps = {
onDeleteGeometry: () => console.log('onDeleteGeometry')
};
WithGeometry.args = WithGeometryProps;

export const SmallSize = Template.bind({});
const SmallSizeProps = {
geometry: {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [0.5, 0.5]
},
properties: {
name: 'Mask'
}
},
size: 'small',
onSelectGeometry: () => console.log('onSelectGeometry'),
onDeleteGeometry: () => console.log('onDeleteGeometry')
};
SmallSize.args = SmallSizeProps;
Loading

0 comments on commit 4d7e9e8

Please sign in to comment.