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;