Skip to content

Commit

Permalink
SelectField: extend to accept more data structures as children and to…
Browse files Browse the repository at this point in the history
… fix placeholder (#796)
  • Loading branch information
vmilan authored Jan 4, 2024
1 parent fc716d3 commit 8d012d1
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 109 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Not released

- SelectField: extend to accept more data structures as children and to fix placeholder [#796](https://github.com/CartoDB/carto-react/pull/796)

## 2.3

### 2.3.5 (2024-01-02)
Expand Down
13 changes: 9 additions & 4 deletions packages/react-ui/src/components/atoms/LabelWithIndicator.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import { Box, styled } from '@mui/material';

import Typography from './Typography';

const Root = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5)
}));

const LabelIndicator = styled(Typography)(({ theme }) => ({
marginLeft: theme.spacing(0.5),
'.Mui-disabled &': {
color: theme.palette.text.disabled
}
Expand All @@ -15,7 +20,7 @@ const LabelWithIndicator = ({ label, type }) => {
const isRequired = type === 'required';

return (
<>
<Root>
{label}
<LabelIndicator
component='span'
Expand All @@ -25,7 +30,7 @@ const LabelWithIndicator = ({ label, type }) => {
>
{isRequired ? '(required)' : '(optional)'}
</LabelIndicator>
</>
</Root>
);
};

Expand Down
25 changes: 13 additions & 12 deletions packages/react-ui/src/components/atoms/SelectField.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { MenuProps } from '@mui/material';
import { SelectProps } from '@mui/material/Select';
import { TextFieldProps } from '@mui/material/TextField';
import React from 'react';
import { InputProps, MenuProps } from '@mui/material';
import { SelectProps } from '@mui/material/Select';

export type SelectFieldProps = Omit<TextFieldProps, 'placeholder'> &
Omit<SelectProps, 'placeholder'> & {
placeholder?: React.ReactNode;
size?: 'small' | 'medium';
selectProps?: Partial<SelectProps<unknown>>;
renderValue?: (value: string[]) => React.ReactNode;
menuProps?: Partial<MenuProps>;
};
export type SelectFieldProps<Value = unknown> = Omit<
SelectProps<Value>,
'placeholder'
> & {
placeholder?: React.ReactNode | string;
size?: 'small' | 'medium';
menuProps?: Partial<MenuProps>;
inputProps?: Partial<InputProps>;
helperText?: React.ReactNode | string;
};

declare const SelectField: (props: SelectFieldProps) => JSX.Element;
declare const SelectField: <Value>(props: SelectFieldProps<Value>) => JSX.Element;
export default SelectField;
138 changes: 91 additions & 47 deletions packages/react-ui/src/components/atoms/SelectField.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
import React, { forwardRef } from 'react';
import React, { forwardRef, useState } from 'react';
import PropTypes from 'prop-types';
import { TextField } from '@mui/material';
import {
FormControl,
FormHelperText,
InputLabel,
MenuItem,
Select,
styled
} from '@mui/material';
import Typography from './Typography';
import uniqueId from 'lodash/uniqueId';

const StyledSelect = styled(Select)(({ theme }) => ({
'& .MuiInputAdornment-positionStart': {
paddingLeft: theme.spacing(2),

'&.MuiInputAdornment-sizeSmall': {
paddingLeft: theme.spacing(1.5)
}
},
'& .MuiInputBase-inputAdornedStart': {
paddingLeft: '0px !important'
},
'& .MuiSelect-select .MuiMenuItem-root:hover': {
backgroundColor: 'transparent'
}
}));

const PlaceholderItem = styled(MenuItem)(() => ({
display: 'none'
}));

const SelectField = forwardRef(
(
{
children,
onChange,
placeholder,
size,
multiple,
displayEmpty,
selectProps,
renderValue,
menuProps,
inputProps,
labelId,
label,
helperText,
name,
error,
focused,
disabled,
fullWidth,
required,
'aria-label': ariaLabel,
...otherProps
},
ref
Expand All @@ -24,40 +59,34 @@ const SelectField = forwardRef(

const isSmall = size === 'small';

const defaultRenderValue = React.useCallback(
(selected) => {
if (selected.length === 0) {
return (
<Typography
variant={isSmall ? 'body2' : 'body1'}
color='text.hint'
component='span'
noWrap
>
{placeholder}
</Typography>
);
}
return selected.join(', ');
},
[isSmall, placeholder]
);
// Accessibility attributes
const [defaultId] = useState(uniqueId('select-label-'));
const ariaLabelledBy = label ? labelId || defaultId : undefined;

return (
<TextField
{...otherProps}
select
onChange={onChange}
ref={ref}
<FormControl
size={size}
placeholder={placeholder}
SelectProps={{
...selectProps,
multiple: multiple,
displayEmpty: displayEmpty || !!placeholder,
size: size,
renderValue: renderValue || defaultRenderValue,
MenuProps: {
error={error}
focused={focused}
disabled={disabled}
fullWidth={fullWidth}
required={required}
>
{label && <InputLabel id={ariaLabelledBy}>{label}</InputLabel>}

<StyledSelect
{...otherProps}
labelId={ariaLabelledBy}
name={name}
ref={ref}
size={size}
fullWidth={fullWidth}
displayEmpty={displayEmpty || !!placeholder}
inputProps={{
...inputProps,
'aria-label': ariaLabel
}}
MenuProps={{
...menuProps,
anchorOrigin: {
vertical: 'bottom',
Expand All @@ -67,25 +96,40 @@ const SelectField = forwardRef(
vertical: -4,
horizontal: 'left'
}
}
}}
>
{children}
</TextField>
}}
>
{placeholder && (
<PlaceholderItem disabled value=''>
<Typography
variant={isSmall ? 'body2' : 'body1'}
color='text.hint'
component='span'
>
{placeholder}
</Typography>
</PlaceholderItem>
)}

{children}
</StyledSelect>

{helperText && (
<FormHelperText aria-label={`${name}-helper`}>{helperText}</FormHelperText>
)}
</FormControl>
);
}
);

SelectField.defaultProps = {
multiple: false,
size: 'small'
};
SelectField.propTypes = {
placeholder: PropTypes.string,
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
size: PropTypes.oneOf(['small', 'medium']),
selectProps: PropTypes.object,
renderValue: PropTypes.func,
menuProps: PropTypes.object
menuProps: PropTypes.object,
inputProps: PropTypes.object,
helperText: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
};

export default SelectField;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import MultipleSelectField from '../../../src/components/atoms/MultipleSelectField';
import { DocContainer, DocHighlight, DocLink } from '../../utils/storyStyles';
import { DocContainer, DocHighlight } from '../../utils/storyStyles';
import Typography from '../../../src/components/atoms/Typography';

const options = {
Expand Down
Loading

0 comments on commit 8d012d1

Please sign in to comment.