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

[Select] Update renderValue prop's TypeScript type #34177

108 changes: 83 additions & 25 deletions packages/mui-material/src/Select/Select.d.ts
Expand Up @@ -9,7 +9,7 @@ import { OutlinedInputProps } from '../OutlinedInput';

export { SelectChangeEvent };

export interface SelectProps<T = unknown>
interface CommonProps<T>
extends StandardProps<InputProps, 'value' | 'onChange'>,
Omit<OutlinedInputProps, 'value' | 'onChange'>,
Pick<SelectInputProps<T>, 'onChange'> {
Expand Down Expand Up @@ -41,17 +41,6 @@ export interface SelectProps<T = unknown>
* The default value. Use when the component is not controlled.
*/
defaultValue?: T;
/**
* If `true`, a value is displayed even if no items are selected.
*
* In order to display a meaningful value, a function can be passed to the `renderValue` prop which
* returns the value to be displayed when no items are selected.
*
* ⚠️ When using this prop, make sure the label doesn't overlap with the empty displayed value.
* The label should either be hidden or forced to a shrunk state.
* @default false
*/
displayEmpty?: boolean;
/**
* The icon that displays the arrow.
* @default ArrowDropDownIcon
Expand Down Expand Up @@ -83,11 +72,6 @@ export interface SelectProps<T = unknown>
* Props applied to the [`Menu`](/material-ui/api/menu/) element.
*/
MenuProps?: Partial<MenuProps>;
/**
* If `true`, `value` must be an array and the menu will support multiple selections.
* @default false
*/
multiple?: boolean;
/**
* If `true`, the component uses a native `select` element.
* @default false
Expand Down Expand Up @@ -121,14 +105,6 @@ export interface SelectProps<T = unknown>
* You can only use it when the `native` prop is `false` (default).
*/
open?: boolean;
/**
* Render the selected value.
* You can only use it when the `native` prop is `false` (default).
*
* @param {any} value The `value` provided to the component.
* @returns {ReactNode}
*/
renderValue?: (value: T) => React.ReactNode;
/**
* Props applied to the clickable div element.
*/
Expand All @@ -152,6 +128,88 @@ export interface SelectProps<T = unknown>
variant?: 'standard' | 'outlined' | 'filled';
}

type ConditionalRenderValueType<T> =
| {
/**
* If `true`, a value is displayed even if no items are selected.
*
* In order to display a meaningful value, a function can be passed to the `renderValue` prop which
* returns the value to be displayed when no items are selected.
*
* ⚠️ When using this prop, make sure the label doesn't overlap with the empty displayed value.
* The label should either be hidden or forced to a shrunk state.
* @default false
*/
displayEmpty?: false;
/**
* If `true`, `value` must be an array and the menu will support multiple selections.
* @default false
*/
multiple?: boolean;
/**
* Render the selected value.
* You can only use it when the `native` prop is `false` (default).
*
* @param {any} value The `value` provided to the component.
* @returns {ReactNode}
*/
renderValue?: (value: T) => React.ReactNode;
}
| {
/**
* If `true`, a value is displayed even if no items are selected.
*
* In order to display a meaningful value, a function can be passed to the `renderValue` prop which
* returns the value to be displayed when no items are selected.
*
* ⚠️ When using this prop, make sure the label doesn't overlap with the empty displayed value.
* The label should either be hidden or forced to a shrunk state.
* @default false
*/
displayEmpty: true;
/**
* If `true`, `value` must be an array and the menu will support multiple selections.
* @default false
*/
multiple?: false;
/**
* Render the selected value.
* You can only use it when the `native` prop is `false` (default).
*
* @param {any} value The `value` provided to the component.
* @returns {ReactNode}
*/
renderValue?: (value: T | '') => React.ReactNode;
}
| {
/**
* If `true`, a value is displayed even if no items are selected.
*
* In order to display a meaningful value, a function can be passed to the `renderValue` prop which
* returns the value to be displayed when no items are selected.
*
* ⚠️ When using this prop, make sure the label doesn't overlap with the empty displayed value.
* The label should either be hidden or forced to a shrunk state.
* @default false
*/
displayEmpty: true;
/**
* If `true`, `value` must be an array and the menu will support multiple selections.
* @default false
*/
multiple: true;
/**
* Render the selected value.
* You can only use it when the `native` prop is `false` (default).
*
* @param {any} value The `value` provided to the component.
* @returns {ReactNode}
*/
renderValue?: (value: T) => React.ReactNode;
};

export type SelectProps<T = unknown> = CommonProps<T> & ConditionalRenderValueType<T>;

/**
*
* Demos:
Expand Down
78 changes: 78 additions & 0 deletions packages/mui-material/src/Select/Select.spec.tsx
Expand Up @@ -45,3 +45,81 @@ function genericValueTest() {
// disabledUnderline prop should be available (inherited from InputProps) and NOT throw typescript error
<Select disableUnderline />;
}

function App() {
enum MyEnum {
FIRST = 'first',
SECOND = 'second',
}

const [selectedValue, setSelectedValue] = React.useState<MyEnum | ''>('');
const [personName, setPersonName] = React.useState<string[]>([]);

return (
<React.Fragment>
{/* displayEmpty is true */}
<Select
renderValue={(value) => {
if (value === '') {
return 'None selected';
}
return value;
}}
displayEmpty
value={selectedValue}
onChange={(e) => setSelectedValue(e.target.value as MyEnum)}
>
<MenuItem value="">
<em>Blank</em>
</MenuItem>
<MenuItem value="first">first</MenuItem>
<MenuItem value="second">second</MenuItem>
</Select>

{/* displayEmpty is false */}
<Select
Copy link
Member

Choose a reason for hiding this comment

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

Could you please also verify the displayEmpty: true; multiple: true case here?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's already covered in a demo, but still added it in spec here now incase the demo is removed in future.

renderValue={(value) => {
// @ts-expect-error value cannot be empty string since displayEmpty is false
if (value === '') {
return 'None selected';
}
return value;
}}
value={selectedValue}
onChange={(e) => setSelectedValue(e.target.value as MyEnum)}
>
<MenuItem value="">
<em>Blank</em>
</MenuItem>
<MenuItem value="first">first</MenuItem>
<MenuItem value="second">second</MenuItem>
</Select>

{/* displayEmpty is true, multiple is true */}
<Select
displayEmpty
multiple
renderValue={(value) => {
// @ts-expect-error value cannot be empty string
if (value === '') {
return 'None selected';
}

if (value.length === 0) {
return <em>Placeholder</em>;
}

return value.join(', ');
}}
value={personName}
onChange={(e) => setPersonName(e.target.value as string[])}
>
<MenuItem value="">
<em>Blank</em>
</MenuItem>
<MenuItem value="Oliver Hansen">Oliver Hansen</MenuItem>
<MenuItem value="Van Henry">Van Henry</MenuItem>
</Select>
</React.Fragment>
);
}