diff --git a/docs/pages/api-docs/rating.md b/docs/pages/api-docs/rating.md
index 467f6dd107f20e..16df18697b8a94 100644
--- a/docs/pages/api-docs/rating.md
+++ b/docs/pages/api-docs/rating.md
@@ -1,5 +1,5 @@
---
-filename: /packages/material-ui-lab/src/Rating/Rating.js
+filename: /packages/material-ui/src/Rating/Rating.js
---
@@ -11,9 +11,9 @@ filename: /packages/material-ui-lab/src/Rating/Rating.js
## Import
```js
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
// or
-import { Rating } from '@material-ui/lab';
+import { Rating } from '@material-ui/core';
```
You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/).
@@ -76,7 +76,7 @@ You can override the style of the component thanks to one of these customization
- With a [global class name](/customization/components/#overriding-styles-with-global-class-names).
- With a theme and an [`overrides` property](/customization/globals/#css).
-If that's not sufficient, you can check the [implementation of the component](https://github.com/mui-org/material-ui/blob/next/packages/material-ui-lab/src/Rating/Rating.js) for more detail.
+If that's not sufficient, you can check the [implementation of the component](https://github.com/mui-org/material-ui/blob/next/packages/material-ui/src/Rating/Rating.js) for more detail.
## Demos
diff --git a/docs/src/modules/utils/getJsxPreview.test.js b/docs/src/modules/utils/getJsxPreview.test.js
index 38fa388e412402..9926fb233238e2 100644
--- a/docs/src/modules/utils/getJsxPreview.test.js
+++ b/docs/src/modules/utils/getJsxPreview.test.js
@@ -7,7 +7,7 @@ describe('getJsxPreview', () => {
getJsxPreview(
`
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
export default function HalfRating() {
return ;
diff --git a/docs/src/pages/components/rating/CustomizedRatings.js b/docs/src/pages/components/rating/CustomizedRatings.js
index 4a0f6ca176af5b..36a13523329600 100644
--- a/docs/src/pages/components/rating/CustomizedRatings.js
+++ b/docs/src/pages/components/rating/CustomizedRatings.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import FavoriteIcon from '@material-ui/icons/Favorite';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import SentimentVeryDissatisfiedIcon from '@material-ui/icons/SentimentVeryDissatisfied';
diff --git a/docs/src/pages/components/rating/CustomizedRatings.tsx b/docs/src/pages/components/rating/CustomizedRatings.tsx
index d21ff4796c457c..be963f2ea7b1c9 100644
--- a/docs/src/pages/components/rating/CustomizedRatings.tsx
+++ b/docs/src/pages/components/rating/CustomizedRatings.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { withStyles } from '@material-ui/core/styles';
-import Rating, { IconContainerProps } from '@material-ui/lab/Rating';
+import Rating, { IconContainerProps } from '@material-ui/core/Rating';
import FavoriteIcon from '@material-ui/icons/Favorite';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import SentimentVeryDissatisfiedIcon from '@material-ui/icons/SentimentVeryDissatisfied';
diff --git a/docs/src/pages/components/rating/HalfRating.js b/docs/src/pages/components/rating/HalfRating.js
index d71955e790816b..6aa5480b2681b3 100644
--- a/docs/src/pages/components/rating/HalfRating.js
+++ b/docs/src/pages/components/rating/HalfRating.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
diff --git a/docs/src/pages/components/rating/HalfRating.tsx b/docs/src/pages/components/rating/HalfRating.tsx
index 31651c5d01a76d..9bdf485d239bf7 100644
--- a/docs/src/pages/components/rating/HalfRating.tsx
+++ b/docs/src/pages/components/rating/HalfRating.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) =>
diff --git a/docs/src/pages/components/rating/HoverRating.js b/docs/src/pages/components/rating/HoverRating.js
index cf59181710298f..c71b6ec1e36d7f 100644
--- a/docs/src/pages/components/rating/HoverRating.js
+++ b/docs/src/pages/components/rating/HoverRating.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Box from '@material-ui/core/Box';
import StarIcon from '@material-ui/icons/Star';
diff --git a/docs/src/pages/components/rating/HoverRating.tsx b/docs/src/pages/components/rating/HoverRating.tsx
index c84fa817b3ae97..4d7679de9802c7 100644
--- a/docs/src/pages/components/rating/HoverRating.tsx
+++ b/docs/src/pages/components/rating/HoverRating.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Box from '@material-ui/core/Box';
import StarIcon from '@material-ui/icons/Star';
diff --git a/docs/src/pages/components/rating/RatingSize.js b/docs/src/pages/components/rating/RatingSize.js
index a135df8d039f41..065e05c89a8f2a 100644
--- a/docs/src/pages/components/rating/RatingSize.js
+++ b/docs/src/pages/components/rating/RatingSize.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
diff --git a/docs/src/pages/components/rating/RatingSize.tsx b/docs/src/pages/components/rating/RatingSize.tsx
index b46fd5e963a096..154334958fa5a4 100644
--- a/docs/src/pages/components/rating/RatingSize.tsx
+++ b/docs/src/pages/components/rating/RatingSize.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) =>
diff --git a/docs/src/pages/components/rating/SimpleRating.js b/docs/src/pages/components/rating/SimpleRating.js
index c88d73a305a575..527e89b96ecac0 100644
--- a/docs/src/pages/components/rating/SimpleRating.js
+++ b/docs/src/pages/components/rating/SimpleRating.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';
diff --git a/docs/src/pages/components/rating/SimpleRating.tsx b/docs/src/pages/components/rating/SimpleRating.tsx
index c0159b871194c6..d0273ef9b30fe7 100644
--- a/docs/src/pages/components/rating/SimpleRating.tsx
+++ b/docs/src/pages/components/rating/SimpleRating.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';
diff --git a/docs/src/pages/components/rating/TextRating.js b/docs/src/pages/components/rating/TextRating.js
index 10025fdec899bf..ffe44129fcf91b 100644
--- a/docs/src/pages/components/rating/TextRating.js
+++ b/docs/src/pages/components/rating/TextRating.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Box from '@material-ui/core/Box';
import StarIcon from '@material-ui/icons/Star';
diff --git a/docs/src/pages/components/rating/TextRating.tsx b/docs/src/pages/components/rating/TextRating.tsx
index 47ddd9fd8c3f66..cc0b0c8193fddc 100644
--- a/docs/src/pages/components/rating/TextRating.tsx
+++ b/docs/src/pages/components/rating/TextRating.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Box from '@material-ui/core/Box';
import StarIcon from '@material-ui/icons/Star';
diff --git a/docs/src/pages/components/rating/rating.md b/docs/src/pages/components/rating/rating.md
index ac782a7c1e8761..fefe99ee82f8b6 100644
--- a/docs/src/pages/components/rating/rating.md
+++ b/docs/src/pages/components/rating/rating.md
@@ -3,7 +3,6 @@ title: Rating React component
components: Rating
githubLabel: 'component: Rating'
waiAria: https://www.w3.org/WAI/tutorials/forms/custom-controls/#a-star-rating
-packageName: '@material-ui/lab'
---
# Rating
diff --git a/docs/src/pages/guides/localization/Locales.js b/docs/src/pages/guides/localization/Locales.js
index 885aaab87b6c63..0ce944466cca18 100644
--- a/docs/src/pages/guides/localization/Locales.js
+++ b/docs/src/pages/guides/localization/Locales.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import TablePagination from '@material-ui/core/TablePagination';
import Pagination from '@material-ui/lab/Pagination';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Autocomplete from '@material-ui/lab/Autocomplete';
import TextField from '@material-ui/core/TextField';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
diff --git a/docs/src/pages/guides/localization/Locales.tsx b/docs/src/pages/guides/localization/Locales.tsx
index d4263865c39820..55cc9b7d582ce8 100644
--- a/docs/src/pages/guides/localization/Locales.tsx
+++ b/docs/src/pages/guides/localization/Locales.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import TablePagination from '@material-ui/core/TablePagination';
import Pagination from '@material-ui/lab/Pagination';
-import Rating from '@material-ui/lab/Rating';
+import Rating from '@material-ui/core/Rating';
import Autocomplete from '@material-ui/lab/Autocomplete';
import TextField from '@material-ui/core/TextField';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
diff --git a/docs/src/pages/guides/migration-v4/migration-v4.md b/docs/src/pages/guides/migration-v4/migration-v4.md
index f679b23667fa48..f6772b797508aa 100644
--- a/docs/src/pages/guides/migration-v4/migration-v4.md
+++ b/docs/src/pages/guides/migration-v4/migration-v4.md
@@ -524,6 +524,13 @@ const theme = createMuitheme({
### Rating
+- Move the component from the lab to the core. The component is now stable.
+
+ ```diff
+ -import Alert from '@material-ui/lab/Rating';
+ +import Alert from '@material-ui/core/Rating';
+ ```
+
- Change the default empty icon to improve accessibility.
If you have a custom `icon` prop but no `emptyIcon` prop, you can restore the previous behavior with:
diff --git a/packages/material-ui-lab/src/Rating/Rating.d.ts b/packages/material-ui-lab/src/Rating/Rating.d.ts
index 092a46394c44ce..1e8656f51f520e 100644
--- a/packages/material-ui-lab/src/Rating/Rating.d.ts
+++ b/packages/material-ui-lab/src/Rating/Rating.d.ts
@@ -1,151 +1,2 @@
-import * as React from 'react';
-import { InternalStandardProps as StandardProps } from '@material-ui/core';
-
-export interface IconContainerProps extends React.HTMLAttributes {
- value: number;
-}
-
-export interface RatingProps
- extends StandardProps, 'children' | 'onChange'> {
- /**
- * Override or extend the styles applied to the component.
- */
- classes?: {
- /** Styles applied to the root element. */
- root?: string;
- /** Styles applied to the root element if `size="small"`. */
- sizeSmall?: string;
- /** Styles applied to the root element if `size="large"`. */
- sizeLarge?: string;
- /** Styles applied to the root element if `readOnly={true}`. */
- readOnly?: string;
- /** Pseudo-class applied to the root element if `disabled={true}`. */
- disabled?: string;
- /** Pseudo-class applied to the root element if keyboard focused. */
- focusVisible?: string;
- /** Visually hide an element. */
- visuallyHidden?: string;
- /** Styles applied to the label elements. */
- label?: string;
- /** Styles applied to the label of the "no value" input when it is active. */
- labelEmptyValueActive?: string;
- /** Styles applied to the icon wrapping elements. */
- icon?: string;
- /** Styles applied to the icon wrapping elements when empty. */
- iconEmpty?: string;
- /** Styles applied to the icon wrapping elements when filled. */
- iconFilled?: string;
- /** Styles applied to the icon wrapping elements when hover. */
- iconHover?: string;
- /** Styles applied to the icon wrapping elements when focus. */
- iconFocus?: string;
- /** Styles applied to the icon wrapping elements when active. */
- iconActive?: string;
- /** Styles applied to the icon wrapping elements when decimals are necessary. */
- decimal?: string;
- };
- /**
- * The default value. Use when the component is not controlled.
- * @default null
- */
- defaultValue?: number;
- /**
- * If `true`, the rating will be disabled.
- * @default false
- */
- disabled?: boolean;
- /**
- * The icon to display when empty.
- * @default
- */
- emptyIcon?: React.ReactNode;
- /**
- * The label read when the rating input is empty.
- * @default 'Empty'
- */
- emptyLabelText?: React.ReactNode;
- /**
- * Accepts a function which returns a string value that provides a user-friendly name for the current value of the rating.
- *
- * For localization purposes, you can use the provided [translations](/guides/localization/).
- *
- * @param {number} value The rating label's value to format.
- * @returns {string}
- *
- * @default function defaultLabelText(value) {
- * return `${value} Star${value !== 1 ? 's' : ''}`;
- * }
- */
- getLabelText?: (value: number) => string;
- /**
- * The icon to display.
- * @default
- */
- icon?: React.ReactNode;
- /**
- * The component containing the icon.
- * @default function IconContainer(props) {
- * const { value, ...other } = props;
- * return ;
- * }
- */
- IconContainerComponent?: React.ElementType;
- /**
- * Maximum rating.
- * @default 5
- */
- max?: number;
- /**
- * The name attribute of the radio `input` elements.
- * This input `name` should be unique within the page.
- * Being unique within a form is insufficient since the `name` is used to generated IDs.
- */
- name?: string;
- /**
- * Callback fired when the value changes.
- *
- * @param {object} event The event source of the callback.
- * @param {number} value The new value.
- */
- onChange?: (event: React.SyntheticEvent, value: number | null) => void;
- /**
- * Callback function that is fired when the hover state changes.
- *
- * @param {object} event The event source of the callback.
- * @param {number} value The new value.
- */
- onChangeActive?: (event: React.SyntheticEvent, value: number) => void;
- /**
- * The minimum increment value change allowed.
- * @default 1
- */
- precision?: number;
- /**
- * Removes all hover effects and pointer events.
- * @default false
- */
- readOnly?: boolean;
- /**
- * The size of the rating.
- * @default 'medium'
- */
- size?: 'small' | 'medium' | 'large';
- /**
- * The rating value.
- */
- value?: number | null;
-}
-
-export type RatingClassKey = keyof NonNullable;
-
-/**
- *
- * Demos:
- *
- * - [Rating](https://material-ui.com/components/rating/)
- *
- * API:
- *
- * - [Rating API](https://material-ui.com/api/rating/)
- */
-export default function Rating(props: RatingProps): JSX.Element;
+export { default } from '@material-ui/core/Rating';
+export * from '@material-ui/core/Rating';
diff --git a/packages/material-ui-lab/src/Rating/Rating.js b/packages/material-ui-lab/src/Rating/Rating.js
index 60ab67e1fc0ec5..96167414ea3915 100644
--- a/packages/material-ui-lab/src/Rating/Rating.js
+++ b/packages/material-ui-lab/src/Rating/Rating.js
@@ -1,581 +1,24 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import clsx from 'clsx';
-import { chainPropTypes } from '@material-ui/utils';
-import { useTheme, withStyles } from '@material-ui/core/styles';
-import {
- capitalize,
- useForkRef,
- useIsFocusVisible,
- useControlled,
- unstable_useId as useId,
-} from '@material-ui/core/utils';
-import { visuallyHidden } from '@material-ui/system';
-import Star from '../internal/svg-icons/Star';
-import StarBorder from '../internal/svg-icons/StarBorder';
-
-function clamp(value, min, max) {
- if (value < min) {
- return min;
- }
- if (value > max) {
- return max;
- }
- return value;
-}
-
-function getDecimalPrecision(num) {
- const decimalPart = num.toString().split('.')[1];
- return decimalPart ? decimalPart.length : 0;
-}
-
-function roundValueToPrecision(value, precision) {
- if (value == null) {
- return value;
- }
-
- const nearest = Math.round(value / precision) * precision;
- return Number(nearest.toFixed(getDecimalPrecision(precision)));
-}
-
-export const styles = (theme) => ({
- /* Styles applied to the root element. */
- root: {
- display: 'inline-flex',
- // Required to position the pristine input absolutely
- position: 'relative',
- fontSize: theme.typography.pxToRem(24),
- color: '#faaf00',
- cursor: 'pointer',
- textAlign: 'left',
- WebkitTapHighlightColor: 'transparent',
- '&$disabled': {
- opacity: theme.palette.action.disabledOpacity,
- pointerEvents: 'none',
- },
- '&$focusVisible $iconActive': {
- outline: '1px solid #999',
- },
- },
- /* Styles applied to the root element if `size="small"`. */
- sizeSmall: {
- fontSize: theme.typography.pxToRem(18),
- },
- /* Styles applied to the root element if `size="large"`. */
- sizeLarge: {
- fontSize: theme.typography.pxToRem(30),
- },
- /* Styles applied to the root element if `readOnly={true}`. */
- readOnly: {
- pointerEvents: 'none',
- },
- /* Pseudo-class applied to the root element if `disabled={true}`. */
- disabled: {},
- /* Pseudo-class applied to the root element if keyboard focused. */
- focusVisible: {},
- /* Visually hide an element. */
- visuallyHidden,
- /* Styles applied to the label elements. */
- label: {
- cursor: 'inherit',
- },
- /* Styles applied to the label of the "no value" input when it is active. */
- labelEmptyValueActive: {
- top: 0,
- bottom: 0,
- position: 'absolute',
- outline: '1px solid #999',
- width: '100%',
- },
- /* Styles applied to the icon wrapping elements. */
- icon: {
- // Fit wrapper to actual icon size.
- display: 'flex',
- transition: theme.transitions.create('transform', {
- duration: theme.transitions.duration.shortest,
- }),
- // Fix mouseLeave issue.
- // https://github.com/facebook/react/issues/4492
- pointerEvents: 'none',
- },
- /* Styles applied to the icon wrapping elements when empty. */
- iconEmpty: {
- color: theme.palette.action.disabled,
- },
- /* Styles applied to the icon wrapping elements when filled. */
- iconFilled: {},
- /* Styles applied to the icon wrapping elements when hover. */
- iconHover: {},
- /* Styles applied to the icon wrapping elements when focus. */
- iconFocus: {},
- /* Styles applied to the icon wrapping elements when active. */
- iconActive: {
- transform: 'scale(1.2)',
- },
- /* Styles applied to the icon wrapping elements when decimals are necessary. */
- decimal: {
- position: 'relative',
- },
-});
-
-function IconContainer(props) {
- const { value, ...other } = props;
- return ;
-}
-
-IconContainer.propTypes = {
- value: PropTypes.number.isRequired,
-};
-
-const defaultIcon = ;
-const defaultEmptyIcon = ;
-
-function defaultLabelText(value) {
- return `${value} Star${value !== 1 ? 's' : ''}`;
-}
-
-const Rating = React.forwardRef(function Rating(props, ref) {
- const {
- classes,
- className,
- defaultValue = null,
- disabled = false,
- emptyIcon = defaultEmptyIcon,
- emptyLabelText = 'Empty',
- getLabelText = defaultLabelText,
- icon = defaultIcon,
- IconContainerComponent = IconContainer,
- max = 5,
- name: nameProp,
- onChange,
- onChangeActive,
- onMouseLeave,
- onMouseMove,
- precision = 1,
- readOnly = false,
- size = 'medium',
- value: valueProp,
- ...other
- } = props;
-
- const name = useId(nameProp);
-
- const [valueDerived, setValueState] = useControlled({
- controlled: valueProp,
- default: defaultValue,
- name: 'Rating',
- });
-
- const valueRounded = roundValueToPrecision(valueDerived, precision);
- const theme = useTheme();
- const [{ hover, focus }, setState] = React.useState({
- hover: -1,
- focus: -1,
- });
-
- let value = valueRounded;
- if (hover !== -1) {
- value = hover;
- }
- if (focus !== -1) {
- value = focus;
- }
-
- const {
- isFocusVisibleRef,
- onBlur: handleBlurVisible,
- onFocus: handleFocusVisible,
- ref: focusVisibleRef,
- } = useIsFocusVisible();
- const [focusVisible, setFocusVisible] = React.useState(false);
-
- const rootRef = React.useRef();
- const handleFocusRef = useForkRef(focusVisibleRef, rootRef);
- const handleRef = useForkRef(handleFocusRef, ref);
-
- const handleMouseMove = (event) => {
- if (onMouseMove) {
- onMouseMove(event);
- }
-
- const rootNode = rootRef.current;
- const { right, left } = rootNode.getBoundingClientRect();
- const { width } = rootNode.firstChild.getBoundingClientRect();
- let percent;
-
- if (theme.direction === 'rtl') {
- percent = (right - event.clientX) / (width * max);
- } else {
- percent = (event.clientX - left) / (width * max);
- }
-
- let newHover = roundValueToPrecision(max * percent + precision / 2, precision);
- newHover = clamp(newHover, precision, max);
-
- setState((prev) =>
- prev.hover === newHover && prev.focus === newHover
- ? prev
- : {
- hover: newHover,
- focus: newHover,
- },
+import React from 'react';
+import Rating from '@material-ui/core/Rating';
+
+let warnedOnce = false;
+
+/**
+ * @ignore - do not document.
+ */
+export default React.forwardRef(function DeprecatedRating(props, ref) {
+ if (!warnedOnce) {
+ console.warn(
+ [
+ 'Material-UI: The Rating component was moved from the lab to the core.',
+ '',
+ "You should use `import { Rating } from '@material-ui/core'`",
+ "or `import Rating from '@material-ui/core/Rating'`",
+ ].join('\n'),
);
- setFocusVisible(false);
-
- if (onChangeActive && hover !== newHover) {
- onChangeActive(event, newHover);
- }
- };
-
- const handleMouseLeave = (event) => {
- if (onMouseLeave) {
- onMouseLeave(event);
- }
-
- const newHover = -1;
- setState({
- hover: newHover,
- focus: newHover,
- });
-
- if (onChangeActive && hover !== newHover) {
- onChangeActive(event, newHover);
- }
- };
-
- const handleChange = (event) => {
- const newValue = parseFloat(event.target.value);
-
- setValueState(newValue);
-
- if (onChange) {
- onChange(event, newValue);
- }
- };
-
- const handleClear = (event) => {
- // Ignore keyboard events
- // https://github.com/facebook/react/issues/7407
- if (event.clientX === 0 && event.clientY === 0) {
- return;
- }
-
- setState({
- hover: -1,
- focus: -1,
- });
-
- setValueState(null);
-
- if (onChange && parseFloat(event.target.value) === valueRounded) {
- onChange(event, null);
- }
- };
-
- const handleFocus = (event) => {
- handleFocusVisible(event);
- if (isFocusVisibleRef.current === true) {
- setFocusVisible(true);
- }
-
- const newFocus = parseFloat(event.target.value);
- setState((prev) => ({
- hover: prev.hover,
- focus: newFocus,
- }));
-
- if (onChangeActive && focus !== newFocus) {
- onChangeActive(event, newFocus);
- }
- };
-
- const handleBlur = (event) => {
- if (hover !== -1) {
- return;
- }
-
- handleBlurVisible(event);
- if (isFocusVisibleRef.current === false) {
- setFocusVisible(false);
- }
-
- const newFocus = -1;
- setState((prev) => ({
- hover: prev.hover,
- focus: newFocus,
- }));
-
- if (onChangeActive && focus !== newFocus) {
- onChangeActive(event, newFocus);
- }
- };
-
- const [emptyValueFocused, setEmptyValueFocused] = React.useState(false);
-
- const item = (state, labelProps) => {
- const id = `${name}-${String(state.value).replace('.', '-')}`;
- const container = (
-
- {emptyIcon && !state.filled ? emptyIcon : icon}
-
- );
-
- if (readOnly) {
- return (
-
- {container}
-
- );
- }
-
- return (
-
-
-
-
- );
- };
-
- return (
-
- {Array.from(new Array(max)).map((_, index) => {
- const itemValue = index + 1;
-
- if (precision < 1) {
- const items = Array.from(new Array(1 / precision));
- return (
-
- {items.map(($, indexDecimal) => {
- const itemDecimalValue = roundValueToPrecision(
- itemValue - 1 + (indexDecimal + 1) * precision,
- precision,
- );
-
- return item(
- {
- value: itemDecimalValue,
- filled: itemDecimalValue <= value,
- hover: itemDecimalValue <= hover,
- focus: itemDecimalValue <= focus,
- checked: itemDecimalValue === valueRounded,
- },
- {
- style:
- items.length - 1 === indexDecimal
- ? {}
- : {
- width:
- itemDecimalValue === value
- ? `${(indexDecimal + 1) * precision * 100}%`
- : '0%',
- overflow: 'hidden',
- zIndex: 1,
- position: 'absolute',
- },
- },
- );
- })}
-
- );
- }
+ warnedOnce = true;
+ }
- return item({
- value: itemValue,
- active: itemValue === value && (hover !== -1 || focus !== -1),
- filled: itemValue <= value,
- hover: itemValue <= hover,
- focus: itemValue <= focus,
- checked: itemValue === valueRounded,
- });
- })}
- {!readOnly && !disabled && valueRounded == null && (
-
- )}
-
- );
+ return ;
});
-
-Rating.propTypes = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the d.ts file and run "yarn proptypes" |
- // ----------------------------------------------------------------------
- /**
- * Override or extend the styles applied to the component.
- */
- classes: PropTypes.object,
- /**
- * @ignore
- */
- className: PropTypes.string,
- /**
- * The default value. Use when the component is not controlled.
- * @default null
- */
- defaultValue: PropTypes.number,
- /**
- * If `true`, the rating will be disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * The icon to display when empty.
- * @default
- */
- emptyIcon: PropTypes.node,
- /**
- * The label read when the rating input is empty.
- * @default 'Empty'
- */
- emptyLabelText: PropTypes.node,
- /**
- * Accepts a function which returns a string value that provides a user-friendly name for the current value of the rating.
- *
- * For localization purposes, you can use the provided [translations](/guides/localization/).
- *
- * @param {number} value The rating label's value to format.
- * @returns {string}
- *
- * @default function defaultLabelText(value) {
- * return `${value} Star${value !== 1 ? 's' : ''}`;
- * }
- */
- getLabelText: PropTypes.func,
- /**
- * The icon to display.
- * @default
- */
- icon: PropTypes.node,
- /**
- * The component containing the icon.
- * @default function IconContainer(props) {
- * const { value, ...other } = props;
- * return ;
- * }
- */
- IconContainerComponent: PropTypes.elementType,
- /**
- * Maximum rating.
- * @default 5
- */
- max: PropTypes.number,
- /**
- * The name attribute of the radio `input` elements.
- * This input `name` should be unique within the page.
- * Being unique within a form is insufficient since the `name` is used to generated IDs.
- */
- name: PropTypes.string,
- /**
- * Callback fired when the value changes.
- *
- * @param {object} event The event source of the callback.
- * @param {number} value The new value.
- */
- onChange: PropTypes.func,
- /**
- * Callback function that is fired when the hover state changes.
- *
- * @param {object} event The event source of the callback.
- * @param {number} value The new value.
- */
- onChangeActive: PropTypes.func,
- /**
- * @ignore
- */
- onMouseLeave: PropTypes.func,
- /**
- * @ignore
- */
- onMouseMove: PropTypes.func,
- /**
- * The minimum increment value change allowed.
- * @default 1
- */
- precision: chainPropTypes(PropTypes.number, (props) => {
- if (props.precision < 0.1) {
- return new Error(
- [
- 'Material-UI: The prop `precision` should be above 0.1.',
- 'A value below this limit has an imperceptible impact.',
- ].join('\n'),
- );
- }
- return null;
- }),
- /**
- * Removes all hover effects and pointer events.
- * @default false
- */
- readOnly: PropTypes.bool,
- /**
- * The size of the rating.
- * @default 'medium'
- */
- size: PropTypes.oneOf(['large', 'medium', 'small']),
- /**
- * The rating value.
- */
- value: PropTypes.number,
-};
-
-export default withStyles(styles, { name: 'MuiRating' })(Rating);
diff --git a/packages/material-ui-lab/src/themeAugmentation/components.d.ts b/packages/material-ui-lab/src/themeAugmentation/components.d.ts
index 9e3e3bd7a7cbef..cec70484dcb348 100644
--- a/packages/material-ui-lab/src/themeAugmentation/components.d.ts
+++ b/packages/material-ui-lab/src/themeAugmentation/components.d.ts
@@ -19,10 +19,6 @@ export interface LabComponents {
styleOverrides?: ComponentsOverrides['MuiPaginationItem'];
variants?: ComponentsVariants['MuiPaginationItem'];
};
- MuiRating?: {
- defaultProps?: ComponentsProps['MuiRating'];
- styleOverrides?: ComponentsOverrides['MuiRating'];
- };
MuiSkeleton?: {
defaultProps?: ComponentsProps['MuiSkeleton'];
styleOverrides?: ComponentsOverrides['MuiSkeleton'];
diff --git a/packages/material-ui-lab/src/themeAugmentation/overrides.d.ts b/packages/material-ui-lab/src/themeAugmentation/overrides.d.ts
index 01e87f683316cc..a4b887b0436656 100644
--- a/packages/material-ui-lab/src/themeAugmentation/overrides.d.ts
+++ b/packages/material-ui-lab/src/themeAugmentation/overrides.d.ts
@@ -2,7 +2,6 @@ import { AutocompleteClassKey } from '../Autocomplete';
import { AvatarGroupClassKey } from '../AvatarGroup';
import { PaginationClassKey } from '../Pagination';
import { PaginationItemClassKey } from '../PaginationItem';
-import { RatingClassKey } from '../Rating';
import { SkeletonClassKey } from '../Skeleton';
import { SpeedDialClassKey } from '../SpeedDial';
import { SpeedDialActionClassKey } from '../SpeedDialAction';
@@ -26,7 +25,6 @@ export interface LabComponentNameToClassKey {
MuiAvatarGroup: AvatarGroupClassKey;
MuiPagination: PaginationClassKey;
MuiPaginationItem: PaginationItemClassKey;
- MuiRating: RatingClassKey;
MuiSkeleton: SkeletonClassKey;
MuiSpeedDial: SpeedDialClassKey;
MuiSpeedDialAction: SpeedDialActionClassKey;
diff --git a/packages/material-ui-lab/src/themeAugmentation/props.d.ts b/packages/material-ui-lab/src/themeAugmentation/props.d.ts
index c13dbfc17e1dcd..673a2b85f0db3c 100644
--- a/packages/material-ui-lab/src/themeAugmentation/props.d.ts
+++ b/packages/material-ui-lab/src/themeAugmentation/props.d.ts
@@ -2,7 +2,6 @@ import { AutocompleteProps } from '../Autocomplete';
import { AvatarGroupProps } from '../AvatarGroup';
import { PaginationProps } from '../Pagination';
import { PaginationItemProps } from '../PaginationItem';
-import { RatingProps } from '../Rating';
import { SkeletonProps } from '../Skeleton';
import { SpeedDialProps } from '../SpeedDial';
import { SpeedDialActionProps } from '../SpeedDialAction';
@@ -26,7 +25,6 @@ export interface LabComponentsPropsList {
MuiAvatarGroup: AvatarGroupProps;
MuiPagination: PaginationProps;
MuiPaginationItem: PaginationItemProps;
- MuiRating: RatingProps;
MuiSkeleton: SkeletonProps;
MuiSpeedDial: SpeedDialProps;
MuiSpeedDialAction: SpeedDialActionProps;
diff --git a/packages/material-ui/src/Rating/Rating.d.ts b/packages/material-ui/src/Rating/Rating.d.ts
new file mode 100644
index 00000000000000..963c4ec17a3d4f
--- /dev/null
+++ b/packages/material-ui/src/Rating/Rating.d.ts
@@ -0,0 +1,151 @@
+import * as React from 'react';
+import { InternalStandardProps as StandardProps } from '..';
+
+export interface IconContainerProps extends React.HTMLAttributes {
+ value: number;
+}
+
+export interface RatingProps
+ extends StandardProps, 'children' | 'onChange'> {
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: {
+ /** Styles applied to the root element. */
+ root?: string;
+ /** Styles applied to the root element if `size="small"`. */
+ sizeSmall?: string;
+ /** Styles applied to the root element if `size="large"`. */
+ sizeLarge?: string;
+ /** Styles applied to the root element if `readOnly={true}`. */
+ readOnly?: string;
+ /** Pseudo-class applied to the root element if `disabled={true}`. */
+ disabled?: string;
+ /** Pseudo-class applied to the root element if keyboard focused. */
+ focusVisible?: string;
+ /** Visually hide an element. */
+ visuallyHidden?: string;
+ /** Styles applied to the label elements. */
+ label?: string;
+ /** Styles applied to the label of the "no value" input when it is active. */
+ labelEmptyValueActive?: string;
+ /** Styles applied to the icon wrapping elements. */
+ icon?: string;
+ /** Styles applied to the icon wrapping elements when empty. */
+ iconEmpty?: string;
+ /** Styles applied to the icon wrapping elements when filled. */
+ iconFilled?: string;
+ /** Styles applied to the icon wrapping elements when hover. */
+ iconHover?: string;
+ /** Styles applied to the icon wrapping elements when focus. */
+ iconFocus?: string;
+ /** Styles applied to the icon wrapping elements when active. */
+ iconActive?: string;
+ /** Styles applied to the icon wrapping elements when decimals are necessary. */
+ decimal?: string;
+ };
+ /**
+ * The default value. Use when the component is not controlled.
+ * @default null
+ */
+ defaultValue?: number;
+ /**
+ * If `true`, the rating will be disabled.
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * The icon to display when empty.
+ * @default
+ */
+ emptyIcon?: React.ReactNode;
+ /**
+ * The label read when the rating input is empty.
+ * @default 'Empty'
+ */
+ emptyLabelText?: React.ReactNode;
+ /**
+ * Accepts a function which returns a string value that provides a user-friendly name for the current value of the rating.
+ *
+ * For localization purposes, you can use the provided [translations](/guides/localization/).
+ *
+ * @param {number} value The rating label's value to format.
+ * @returns {string}
+ *
+ * @default function defaultLabelText(value) {
+ * return `${value} Star${value !== 1 ? 's' : ''}`;
+ * }
+ */
+ getLabelText?: (value: number) => string;
+ /**
+ * The icon to display.
+ * @default
+ */
+ icon?: React.ReactNode;
+ /**
+ * The component containing the icon.
+ * @default function IconContainer(props) {
+ * const { value, ...other } = props;
+ * return ;
+ * }
+ */
+ IconContainerComponent?: React.ElementType;
+ /**
+ * Maximum rating.
+ * @default 5
+ */
+ max?: number;
+ /**
+ * The name attribute of the radio `input` elements.
+ * This input `name` should be unique within the page.
+ * Being unique within a form is insufficient since the `name` is used to generated IDs.
+ */
+ name?: string;
+ /**
+ * Callback fired when the value changes.
+ *
+ * @param {object} event The event source of the callback.
+ * @param {number} value The new value.
+ */
+ onChange?: (event: React.SyntheticEvent, value: number | null) => void;
+ /**
+ * Callback function that is fired when the hover state changes.
+ *
+ * @param {object} event The event source of the callback.
+ * @param {number} value The new value.
+ */
+ onChangeActive?: (event: React.SyntheticEvent, value: number) => void;
+ /**
+ * The minimum increment value change allowed.
+ * @default 1
+ */
+ precision?: number;
+ /**
+ * Removes all hover effects and pointer events.
+ * @default false
+ */
+ readOnly?: boolean;
+ /**
+ * The size of the rating.
+ * @default 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+ /**
+ * The rating value.
+ */
+ value?: number | null;
+}
+
+export type RatingClassKey = keyof NonNullable;
+
+/**
+ *
+ * Demos:
+ *
+ * - [Rating](https://material-ui.com/components/rating/)
+ *
+ * API:
+ *
+ * - [Rating API](https://material-ui.com/api/rating/)
+ */
+export default function Rating(props: RatingProps): JSX.Element;
diff --git a/packages/material-ui/src/Rating/Rating.js b/packages/material-ui/src/Rating/Rating.js
new file mode 100644
index 00000000000000..e89278b2b8a7d3
--- /dev/null
+++ b/packages/material-ui/src/Rating/Rating.js
@@ -0,0 +1,581 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { visuallyHidden } from '@material-ui/system';
+import { chainPropTypes } from '@material-ui/utils';
+import { useTheme, withStyles } from '../styles';
+import {
+ capitalize,
+ useForkRef,
+ useIsFocusVisible,
+ useControlled,
+ unstable_useId as useId,
+} from '../utils';
+import Star from '../internal/svg-icons/Star';
+import StarBorder from '../internal/svg-icons/StarBorder';
+
+function clamp(value, min, max) {
+ if (value < min) {
+ return min;
+ }
+ if (value > max) {
+ return max;
+ }
+ return value;
+}
+
+function getDecimalPrecision(num) {
+ const decimalPart = num.toString().split('.')[1];
+ return decimalPart ? decimalPart.length : 0;
+}
+
+function roundValueToPrecision(value, precision) {
+ if (value == null) {
+ return value;
+ }
+
+ const nearest = Math.round(value / precision) * precision;
+ return Number(nearest.toFixed(getDecimalPrecision(precision)));
+}
+
+export const styles = (theme) => ({
+ /* Styles applied to the root element. */
+ root: {
+ display: 'inline-flex',
+ // Required to position the pristine input absolutely
+ position: 'relative',
+ fontSize: theme.typography.pxToRem(24),
+ color: '#faaf00',
+ cursor: 'pointer',
+ textAlign: 'left',
+ WebkitTapHighlightColor: 'transparent',
+ '&$disabled': {
+ opacity: theme.palette.action.disabledOpacity,
+ pointerEvents: 'none',
+ },
+ '&$focusVisible $iconActive': {
+ outline: '1px solid #999',
+ },
+ },
+ /* Styles applied to the root element if `size="small"`. */
+ sizeSmall: {
+ fontSize: theme.typography.pxToRem(18),
+ },
+ /* Styles applied to the root element if `size="large"`. */
+ sizeLarge: {
+ fontSize: theme.typography.pxToRem(30),
+ },
+ /* Styles applied to the root element if `readOnly={true}`. */
+ readOnly: {
+ pointerEvents: 'none',
+ },
+ /* Pseudo-class applied to the root element if `disabled={true}`. */
+ disabled: {},
+ /* Pseudo-class applied to the root element if keyboard focused. */
+ focusVisible: {},
+ /* Visually hide an element. */
+ visuallyHidden,
+ /* Styles applied to the label elements. */
+ label: {
+ cursor: 'inherit',
+ },
+ /* Styles applied to the label of the "no value" input when it is active. */
+ labelEmptyValueActive: {
+ top: 0,
+ bottom: 0,
+ position: 'absolute',
+ outline: '1px solid #999',
+ width: '100%',
+ },
+ /* Styles applied to the icon wrapping elements. */
+ icon: {
+ // Fit wrapper to actual icon size.
+ display: 'flex',
+ transition: theme.transitions.create('transform', {
+ duration: theme.transitions.duration.shortest,
+ }),
+ // Fix mouseLeave issue.
+ // https://github.com/facebook/react/issues/4492
+ pointerEvents: 'none',
+ },
+ /* Styles applied to the icon wrapping elements when empty. */
+ iconEmpty: {
+ color: theme.palette.action.disabled,
+ },
+ /* Styles applied to the icon wrapping elements when filled. */
+ iconFilled: {},
+ /* Styles applied to the icon wrapping elements when hover. */
+ iconHover: {},
+ /* Styles applied to the icon wrapping elements when focus. */
+ iconFocus: {},
+ /* Styles applied to the icon wrapping elements when active. */
+ iconActive: {
+ transform: 'scale(1.2)',
+ },
+ /* Styles applied to the icon wrapping elements when decimals are necessary. */
+ decimal: {
+ position: 'relative',
+ },
+});
+
+function IconContainer(props) {
+ const { value, ...other } = props;
+ return ;
+}
+
+IconContainer.propTypes = {
+ value: PropTypes.number.isRequired,
+};
+
+const defaultIcon = ;
+const defaultEmptyIcon = ;
+
+function defaultLabelText(value) {
+ return `${value} Star${value !== 1 ? 's' : ''}`;
+}
+
+const Rating = React.forwardRef(function Rating(props, ref) {
+ const {
+ classes,
+ className,
+ defaultValue = null,
+ disabled = false,
+ emptyIcon = defaultEmptyIcon,
+ emptyLabelText = 'Empty',
+ getLabelText = defaultLabelText,
+ icon = defaultIcon,
+ IconContainerComponent = IconContainer,
+ max = 5,
+ name: nameProp,
+ onChange,
+ onChangeActive,
+ onMouseLeave,
+ onMouseMove,
+ precision = 1,
+ readOnly = false,
+ size = 'medium',
+ value: valueProp,
+ ...other
+ } = props;
+
+ const name = useId(nameProp);
+
+ const [valueDerived, setValueState] = useControlled({
+ controlled: valueProp,
+ default: defaultValue,
+ name: 'Rating',
+ });
+
+ const valueRounded = roundValueToPrecision(valueDerived, precision);
+ const theme = useTheme();
+ const [{ hover, focus }, setState] = React.useState({
+ hover: -1,
+ focus: -1,
+ });
+
+ let value = valueRounded;
+ if (hover !== -1) {
+ value = hover;
+ }
+ if (focus !== -1) {
+ value = focus;
+ }
+
+ const {
+ isFocusVisibleRef,
+ onBlur: handleBlurVisible,
+ onFocus: handleFocusVisible,
+ ref: focusVisibleRef,
+ } = useIsFocusVisible();
+ const [focusVisible, setFocusVisible] = React.useState(false);
+
+ const rootRef = React.useRef();
+ const handleFocusRef = useForkRef(focusVisibleRef, rootRef);
+ const handleRef = useForkRef(handleFocusRef, ref);
+
+ const handleMouseMove = (event) => {
+ if (onMouseMove) {
+ onMouseMove(event);
+ }
+
+ const rootNode = rootRef.current;
+ const { right, left } = rootNode.getBoundingClientRect();
+ const { width } = rootNode.firstChild.getBoundingClientRect();
+ let percent;
+
+ if (theme.direction === 'rtl') {
+ percent = (right - event.clientX) / (width * max);
+ } else {
+ percent = (event.clientX - left) / (width * max);
+ }
+
+ let newHover = roundValueToPrecision(max * percent + precision / 2, precision);
+ newHover = clamp(newHover, precision, max);
+
+ setState((prev) =>
+ prev.hover === newHover && prev.focus === newHover
+ ? prev
+ : {
+ hover: newHover,
+ focus: newHover,
+ },
+ );
+
+ setFocusVisible(false);
+
+ if (onChangeActive && hover !== newHover) {
+ onChangeActive(event, newHover);
+ }
+ };
+
+ const handleMouseLeave = (event) => {
+ if (onMouseLeave) {
+ onMouseLeave(event);
+ }
+
+ const newHover = -1;
+ setState({
+ hover: newHover,
+ focus: newHover,
+ });
+
+ if (onChangeActive && hover !== newHover) {
+ onChangeActive(event, newHover);
+ }
+ };
+
+ const handleChange = (event) => {
+ const newValue = parseFloat(event.target.value);
+
+ setValueState(newValue);
+
+ if (onChange) {
+ onChange(event, newValue);
+ }
+ };
+
+ const handleClear = (event) => {
+ // Ignore keyboard events
+ // https://github.com/facebook/react/issues/7407
+ if (event.clientX === 0 && event.clientY === 0) {
+ return;
+ }
+
+ setState({
+ hover: -1,
+ focus: -1,
+ });
+
+ setValueState(null);
+
+ if (onChange && parseFloat(event.target.value) === valueRounded) {
+ onChange(event, null);
+ }
+ };
+
+ const handleFocus = (event) => {
+ handleFocusVisible(event);
+ if (isFocusVisibleRef.current === true) {
+ setFocusVisible(true);
+ }
+
+ const newFocus = parseFloat(event.target.value);
+ setState((prev) => ({
+ hover: prev.hover,
+ focus: newFocus,
+ }));
+
+ if (onChangeActive && focus !== newFocus) {
+ onChangeActive(event, newFocus);
+ }
+ };
+
+ const handleBlur = (event) => {
+ if (hover !== -1) {
+ return;
+ }
+
+ handleBlurVisible(event);
+ if (isFocusVisibleRef.current === false) {
+ setFocusVisible(false);
+ }
+
+ const newFocus = -1;
+ setState((prev) => ({
+ hover: prev.hover,
+ focus: newFocus,
+ }));
+
+ if (onChangeActive && focus !== newFocus) {
+ onChangeActive(event, newFocus);
+ }
+ };
+
+ const [emptyValueFocused, setEmptyValueFocused] = React.useState(false);
+
+ const item = (state, labelProps) => {
+ const id = `${name}-${String(state.value).replace('.', '-')}`;
+ const container = (
+
+ {emptyIcon && !state.filled ? emptyIcon : icon}
+
+ );
+
+ if (readOnly) {
+ return (
+
+ {container}
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+ };
+
+ return (
+
+ {Array.from(new Array(max)).map((_, index) => {
+ const itemValue = index + 1;
+
+ if (precision < 1) {
+ const items = Array.from(new Array(1 / precision));
+ return (
+
+ {items.map(($, indexDecimal) => {
+ const itemDecimalValue = roundValueToPrecision(
+ itemValue - 1 + (indexDecimal + 1) * precision,
+ precision,
+ );
+
+ return item(
+ {
+ value: itemDecimalValue,
+ filled: itemDecimalValue <= value,
+ hover: itemDecimalValue <= hover,
+ focus: itemDecimalValue <= focus,
+ checked: itemDecimalValue === valueRounded,
+ },
+ {
+ style:
+ items.length - 1 === indexDecimal
+ ? {}
+ : {
+ width:
+ itemDecimalValue === value
+ ? `${(indexDecimal + 1) * precision * 100}%`
+ : '0%',
+ overflow: 'hidden',
+ zIndex: 1,
+ position: 'absolute',
+ },
+ },
+ );
+ })}
+
+ );
+ }
+
+ return item({
+ value: itemValue,
+ active: itemValue === value && (hover !== -1 || focus !== -1),
+ filled: itemValue <= value,
+ hover: itemValue <= hover,
+ focus: itemValue <= focus,
+ checked: itemValue === valueRounded,
+ });
+ })}
+ {!readOnly && !disabled && valueRounded == null && (
+
+ )}
+
+ );
+});
+
+Rating.propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The default value. Use when the component is not controlled.
+ * @default null
+ */
+ defaultValue: PropTypes.number,
+ /**
+ * If `true`, the rating will be disabled.
+ * @default false
+ */
+ disabled: PropTypes.bool,
+ /**
+ * The icon to display when empty.
+ * @default
+ */
+ emptyIcon: PropTypes.node,
+ /**
+ * The label read when the rating input is empty.
+ * @default 'Empty'
+ */
+ emptyLabelText: PropTypes.node,
+ /**
+ * Accepts a function which returns a string value that provides a user-friendly name for the current value of the rating.
+ *
+ * For localization purposes, you can use the provided [translations](/guides/localization/).
+ *
+ * @param {number} value The rating label's value to format.
+ * @returns {string}
+ *
+ * @default function defaultLabelText(value) {
+ * return `${value} Star${value !== 1 ? 's' : ''}`;
+ * }
+ */
+ getLabelText: PropTypes.func,
+ /**
+ * The icon to display.
+ * @default
+ */
+ icon: PropTypes.node,
+ /**
+ * The component containing the icon.
+ * @default function IconContainer(props) {
+ * const { value, ...other } = props;
+ * return ;
+ * }
+ */
+ IconContainerComponent: PropTypes.elementType,
+ /**
+ * Maximum rating.
+ * @default 5
+ */
+ max: PropTypes.number,
+ /**
+ * The name attribute of the radio `input` elements.
+ * This input `name` should be unique within the page.
+ * Being unique within a form is insufficient since the `name` is used to generated IDs.
+ */
+ name: PropTypes.string,
+ /**
+ * Callback fired when the value changes.
+ *
+ * @param {object} event The event source of the callback.
+ * @param {number} value The new value.
+ */
+ onChange: PropTypes.func,
+ /**
+ * Callback function that is fired when the hover state changes.
+ *
+ * @param {object} event The event source of the callback.
+ * @param {number} value The new value.
+ */
+ onChangeActive: PropTypes.func,
+ /**
+ * @ignore
+ */
+ onMouseLeave: PropTypes.func,
+ /**
+ * @ignore
+ */
+ onMouseMove: PropTypes.func,
+ /**
+ * The minimum increment value change allowed.
+ * @default 1
+ */
+ precision: chainPropTypes(PropTypes.number, (props) => {
+ if (props.precision < 0.1) {
+ return new Error(
+ [
+ 'Material-UI: The prop `precision` should be above 0.1.',
+ 'A value below this limit has an imperceptible impact.',
+ ].join('\n'),
+ );
+ }
+ return null;
+ }),
+ /**
+ * Removes all hover effects and pointer events.
+ * @default false
+ */
+ readOnly: PropTypes.bool,
+ /**
+ * The size of the rating.
+ * @default 'medium'
+ */
+ size: PropTypes.oneOf(['large', 'medium', 'small']),
+ /**
+ * The rating value.
+ */
+ value: PropTypes.number,
+};
+
+export default withStyles(styles, { name: 'MuiRating' })(Rating);
diff --git a/packages/material-ui-lab/src/Rating/Rating.test.js b/packages/material-ui/src/Rating/Rating.test.js
similarity index 100%
rename from packages/material-ui-lab/src/Rating/Rating.test.js
rename to packages/material-ui/src/Rating/Rating.test.js
diff --git a/packages/material-ui/src/Rating/index.d.ts b/packages/material-ui/src/Rating/index.d.ts
new file mode 100644
index 00000000000000..88cba844f6235c
--- /dev/null
+++ b/packages/material-ui/src/Rating/index.d.ts
@@ -0,0 +1,2 @@
+export { default } from './Rating';
+export * from './Rating';
diff --git a/packages/material-ui/src/Rating/index.js b/packages/material-ui/src/Rating/index.js
new file mode 100644
index 00000000000000..4da188eae322b9
--- /dev/null
+++ b/packages/material-ui/src/Rating/index.js
@@ -0,0 +1 @@
+export { default } from './Rating';
diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts
index 35108fbe2391f8..a31f94411c7c4f 100644
--- a/packages/material-ui/src/index.d.ts
+++ b/packages/material-ui/src/index.d.ts
@@ -320,6 +320,9 @@ export * from './Radio';
export { default as RadioGroup } from './RadioGroup';
export * from './RadioGroup';
+export { default as Rating } from './Rating';
+export * from './Rating';
+
export { default as ScopedCssBaseline } from './ScopedCssBaseline';
export * from './ScopedCssBaseline';
diff --git a/packages/material-ui/src/index.js b/packages/material-ui/src/index.js
index 0cb8381dc7924c..e26709678125c5 100644
--- a/packages/material-ui/src/index.js
+++ b/packages/material-ui/src/index.js
@@ -246,6 +246,9 @@ export * from './Radio';
export { default as RadioGroup } from './RadioGroup';
export * from './RadioGroup';
+export { default as Rating } from './Rating';
+export * from './Rating';
+
export { default as ScopedCssBaseline } from './ScopedCssBaseline';
export * from './ScopedCssBaseline';
diff --git a/packages/material-ui-lab/src/internal/svg-icons/Star.js b/packages/material-ui/src/internal/svg-icons/Star.js
similarity index 79%
rename from packages/material-ui-lab/src/internal/svg-icons/Star.js
rename to packages/material-ui/src/internal/svg-icons/Star.js
index 511a39c75401ad..4d5f2e5fa9c25e 100644
--- a/packages/material-ui-lab/src/internal/svg-icons/Star.js
+++ b/packages/material-ui/src/internal/svg-icons/Star.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { createSvgIcon } from '@material-ui/core/utils';
+import createSvgIcon from '../../utils/createSvgIcon';
/**
* @ignore - internal component.
diff --git a/packages/material-ui-lab/src/internal/svg-icons/StarBorder.js b/packages/material-ui/src/internal/svg-icons/StarBorder.js
similarity index 85%
rename from packages/material-ui-lab/src/internal/svg-icons/StarBorder.js
rename to packages/material-ui/src/internal/svg-icons/StarBorder.js
index 83ba76d5a25850..aae01550ff0b4c 100644
--- a/packages/material-ui-lab/src/internal/svg-icons/StarBorder.js
+++ b/packages/material-ui/src/internal/svg-icons/StarBorder.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { createSvgIcon } from '@material-ui/core/utils';
+import createSvgIcon from '../../utils/createSvgIcon';
/**
* @ignore - internal component.
diff --git a/packages/material-ui/src/styles/overrides.d.ts b/packages/material-ui/src/styles/overrides.d.ts
index 7e35ad6e14a672..1d2210977bf35b 100644
--- a/packages/material-ui/src/styles/overrides.d.ts
+++ b/packages/material-ui/src/styles/overrides.d.ts
@@ -67,6 +67,7 @@ import { OutlinedInputClassKey } from '../OutlinedInput';
import { PaperClassKey } from '../Paper';
import { PopoverClassKey } from '../Popover';
import { RadioClassKey } from '../Radio';
+import { RatingClassKey } from '../Rating';
import { ScopedCssBaselineClassKey } from '../ScopedCssBaseline';
import { SelectClassKey } from '../Select';
import { SliderClassKey } from '../Slider';
@@ -181,6 +182,7 @@ export interface ComponentNameToClassKey {
MuiPaper: PaperClassKey;
MuiPopover: PopoverClassKey;
MuiRadio: RadioClassKey;
+ MuiRating: RatingClassKey;
MuiScopedCssBaseline: ScopedCssBaselineClassKey;
MuiSelect: SelectClassKey;
MuiSlider: SliderClassKey;
diff --git a/packages/material-ui/src/styles/props.d.ts b/packages/material-ui/src/styles/props.d.ts
index 73cb9aaa6370c8..7c4f6ef1f94040 100644
--- a/packages/material-ui/src/styles/props.d.ts
+++ b/packages/material-ui/src/styles/props.d.ts
@@ -71,6 +71,7 @@ import { PaperProps } from '../Paper';
import { PopoverProps } from '../Popover';
import { RadioGroupProps } from '../RadioGroup';
import { RadioProps } from '../Radio';
+import { RatingProps } from '../Rating';
import { ScopedCssBaselineProps } from '../ScopedCssBaseline';
import { SelectProps } from '../Select';
import { SliderProps } from '../Slider';
@@ -181,6 +182,7 @@ export interface ComponentsPropsList {
MuiPopover: PopoverProps;
MuiRadio: RadioProps;
MuiRadioGroup: RadioGroupProps;
+ MuiRating: RatingProps;
MuiScopedCssBaseline: ScopedCssBaselineProps;
MuiSelect: SelectProps;
MuiSlider: SliderProps;