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

FeatureSelection widget fixes #708

Merged
merged 13 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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,
juandjara marked this conversation as resolved.
Show resolved Hide resolved
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