diff --git a/docs/pages/api-docs/breadcrumbs.json b/docs/pages/api-docs/breadcrumbs.json index 3b850e5caa84f8..9caa03a2639b55 100644 --- a/docs/pages/api-docs/breadcrumbs.json +++ b/docs/pages/api-docs/breadcrumbs.json @@ -7,7 +7,8 @@ "itemsAfterCollapse": { "type": { "name": "number" }, "default": "1" }, "itemsBeforeCollapse": { "type": { "name": "number" }, "default": "1" }, "maxItems": { "type": { "name": "number" }, "default": "8" }, - "separator": { "type": { "name": "node" }, "default": "'/'" } + "separator": { "type": { "name": "node" }, "default": "'/'" }, + "sx": { "type": { "name": "object" } } }, "name": "Breadcrumbs", "styles": { @@ -20,6 +21,6 @@ "filename": "/packages/material-ui/src/Breadcrumbs/Breadcrumbs.js", "inheritance": null, "demos": "", - "styledComponent": false, + "styledComponent": true, "cssComponent": false } diff --git a/docs/translations/api-docs/breadcrumbs/breadcrumbs.json b/docs/translations/api-docs/breadcrumbs/breadcrumbs.json index bbcf7474ae8905..e4aaaa78030013 100644 --- a/docs/translations/api-docs/breadcrumbs/breadcrumbs.json +++ b/docs/translations/api-docs/breadcrumbs/breadcrumbs.json @@ -8,7 +8,8 @@ "itemsAfterCollapse": "If max items is exceeded, the number of items to show after the ellipsis.", "itemsBeforeCollapse": "If max items is exceeded, the number of items to show before the ellipsis.", "maxItems": "Specifies the maximum number of breadcrumbs to display. When there are more than the maximum number, only the first itemsBeforeCollapse and last itemsAfterCollapse will be shown, with an ellipsis in between.", - "separator": "Custom separator node." + "separator": "Custom separator node.", + "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details." }, "classDescriptions": { "root": { "description": "Styles applied to the root element." }, diff --git a/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.js b/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.js index 046a79ae6654a8..4390f3108da474 100644 --- a/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.js +++ b/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.js @@ -1,52 +1,89 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import withStyles from '../styles/withStyles'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import experimentalStyled from '../styles/experimentalStyled'; import { emphasize } from '../styles/colorManipulator'; import MoreHorizIcon from '../internal/svg-icons/MoreHoriz'; import ButtonBase from '../ButtonBase'; +import { getBreadcrumbCollapsedUtilityClass } from './breadcrumbCollapsedClasses'; -const styles = (theme) => ({ - button: { - display: 'flex', - marginLeft: theme.spacing(0.5), - marginRight: theme.spacing(0.5), - backgroundColor: theme.palette.grey[100], - color: theme.palette.grey[700], - borderRadius: 2, - '&:hover, &:focus': { - backgroundColor: theme.palette.grey[200], - }, - '&:active': { - boxShadow: theme.shadows[0], - backgroundColor: emphasize(theme.palette.grey[200], 0.12), - }, +const useUtilityClasses = (styleProps) => { + const { classes } = styleProps; + + const slots = { + button: ['button'], + icon: ['icon'], + }; + + return composeClasses(slots, getBreadcrumbCollapsedUtilityClass, classes); +}; + +const BreadcrumbCollapsedButton = experimentalStyled( + ButtonBase, + {}, + { + name: 'PrivateBreadcrumbCollapsed', + slot: 'Button', }, - icon: { - width: 24, - height: 16, +)(({ theme }) => ({ + display: 'flex', + marginLeft: theme.spacing(0.5), + marginRight: theme.spacing(0.5), + ...(theme.palette.mode === 'light' + ? { backgroundColor: theme.palette.grey[100], color: theme.palette.grey[700] } + : { backgroundColor: theme.palette.grey[700], color: theme.palette.grey[100] }), + borderRadius: 2, + '&:hover, &:focus': { + ...(theme.palette.mode === 'light' + ? { backgroundColor: theme.palette.grey[200] } + : { backgroundColor: theme.palette.grey[600] }), + }, + '&:active': { + boxShadow: theme.shadows[0], + ...(theme.palette.mode === 'light' + ? { backgroundColor: emphasize(theme.palette.grey[200], 0.12) } + : { backgroundColor: emphasize(theme.palette.grey[600], 0.12) }), + }, +})); + +const BreadcrumbCollapsedIcon = experimentalStyled( + MoreHorizIcon, + {}, + { + name: 'PrivateBreadcrumbCollapsed', + slot: 'Icon', }, +)({ + width: 24, + height: 16, }); /** * @ignore - internal component. */ function BreadcrumbCollapsed(props) { - const { classes, ...other } = props; + const styleProps = props; + const classes = useUtilityClasses(styleProps); return (
  • - - - + + +
  • ); } BreadcrumbCollapsed.propTypes = { /** - * @ignore + * The system prop that allows defining system overrides as well as additional CSS styles. */ - classes: PropTypes.object.isRequired, + sx: PropTypes.object, }; -export default withStyles(styles, { name: 'PrivateBreadcrumbCollapsed' })(BreadcrumbCollapsed); +export default BreadcrumbCollapsed; diff --git a/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.test.js b/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.test.js index 796026fff7ca2b..43ad3c5f2c43b7 100644 --- a/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.test.js +++ b/packages/material-ui/src/Breadcrumbs/BreadcrumbCollapsed.test.js @@ -1,17 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { getClasses, fireEvent, createClientRender } from 'test/utils'; +import { fireEvent, createClientRender } from 'test/utils'; import BreadcrumbCollapsed from './BreadcrumbCollapsed'; +import classes from './breadcrumbCollapsedClasses'; describe('', () => { - let classes; const render = createClientRender(); - before(() => { - classes = getClasses(); - }); - it('should render an icon', () => { const { container, getByRole } = render(); diff --git a/packages/material-ui/src/Breadcrumbs/Breadcrumbs.d.ts b/packages/material-ui/src/Breadcrumbs/Breadcrumbs.d.ts index 42fda3ca4616d9..1bd6b5553ed6c0 100644 --- a/packages/material-ui/src/Breadcrumbs/Breadcrumbs.d.ts +++ b/packages/material-ui/src/Breadcrumbs/Breadcrumbs.d.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import { SxProps } from '@material-ui/system'; +import { Theme } from '@material-ui/core/styles'; import { OverridableComponent, OverrideProps } from '../OverridableComponent'; export interface BreadcrumbsTypeMap

    { @@ -49,6 +51,10 @@ export interface BreadcrumbsTypeMap

    * @default '/' */ separator?: React.ReactNode; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; }; defaultComponent: D; } diff --git a/packages/material-ui/src/Breadcrumbs/Breadcrumbs.js b/packages/material-ui/src/Breadcrumbs/Breadcrumbs.js index a18f02e091569e..9991ac2fa51bad 100644 --- a/packages/material-ui/src/Breadcrumbs/Breadcrumbs.js +++ b/packages/material-ui/src/Breadcrumbs/Breadcrumbs.js @@ -2,41 +2,88 @@ import * as React from 'react'; import { isFragment } from 'react-is'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import withStyles from '../styles/withStyles'; +import { deepmerge } from '@material-ui/utils'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import experimentalStyled from '../styles/experimentalStyled'; +import useThemeProps from '../styles/useThemeProps'; import Typography from '../Typography'; import BreadcrumbCollapsed from './BreadcrumbCollapsed'; +import breadcrumbsClasses, { getBreadcrumbsUtilityClass } from './breadcrumbsClasses'; -export const styles = { - /* Styles applied to the root element. */ - root: {}, - /* Styles applied to the ol element. */ - ol: { - display: 'flex', - flexWrap: 'wrap', - alignItems: 'center', - padding: 0, - margin: 0, - listStyle: 'none', +const overridesResolver = (props, styles) => { + return deepmerge(styles.root || {}, { + [`& .${breadcrumbsClasses.ol}`]: styles.ol, + [`& .${breadcrumbsClasses.li}`]: styles.li, + [`& .${breadcrumbsClasses.separator}`]: styles.separator, + }); +}; + +const useUtilityClasses = (styleProps) => { + const { classes } = styleProps; + + const slots = { + root: ['root'], + li: ['li'], + ol: ['ol'], + separator: ['separator'], + }; + + return composeClasses(slots, getBreadcrumbsUtilityClass, classes); +}; + +const BreadcrumbsRoot = experimentalStyled( + Typography, + {}, + { + name: 'MuiBreadcrumbs', + slot: 'Root', + overridesResolver, }, - /* Styles applied to the li element. */ - li: {}, - /* Styles applied to the separator element. */ - separator: { - display: 'flex', - userSelect: 'none', - marginLeft: 8, - marginRight: 8, +)({}); + +const BreadcrumbsOl = experimentalStyled( + 'ol', + {}, + { + name: 'MuiBreadcrumbs', + slot: 'Ol', }, -}; +)({ + display: 'flex', + flexWrap: 'wrap', + alignItems: 'center', + padding: 0, + margin: 0, + listStyle: 'none', +}); -function insertSeparators(items, className, separator) { +const BreadcrumbsSeparator = experimentalStyled( + 'li', + {}, + { + name: 'MuiBreadcrumbs', + slot: 'Separator', + }, +)({ + display: 'flex', + userSelect: 'none', + marginLeft: 8, + marginRight: 8, +}); + +function insertSeparators(items, className, separator, styleProps) { return items.reduce((acc, current, index) => { if (index < items.length - 1) { acc = acc.concat( current, -

  • + {separator} -
  • , + , ); } else { acc.push(current); @@ -46,12 +93,12 @@ function insertSeparators(items, className, separator) { }, []); } -const Breadcrumbs = React.forwardRef(function Breadcrumbs(props, ref) { +const Breadcrumbs = React.forwardRef(function Breadcrumbs(inProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiBreadcrumbs' }); const { children, - classes, className, - component: Component = 'nav', + component = 'nav', expandText = 'Show path', itemsAfterCollapse = 1, itemsBeforeCollapse = 1, @@ -62,6 +109,19 @@ const Breadcrumbs = React.forwardRef(function Breadcrumbs(props, ref) { const [expanded, setExpanded] = React.useState(false); + const styleProps = { + ...props, + component, + expanded, + expandText, + itemsAfterCollapse, + itemsBeforeCollapse, + maxItems, + separator, + }; + + const classes = useUtilityClasses(styleProps); + const listRef = React.useRef(null); const renderItemsBeforeAndAfter = (allItems) => { const handleClickExpand = () => { @@ -120,23 +180,25 @@ const Breadcrumbs = React.forwardRef(function Breadcrumbs(props, ref) { )); return ( - -
      + {insertSeparators( expanded || (maxItems && allItems.length <= maxItems) ? allItems : renderItemsBeforeAndAfter(allItems), classes.separator, separator, + styleProps, )} -
    -
    + + ); }); @@ -191,6 +253,10 @@ Breadcrumbs.propTypes = { * @default '/' */ separator: PropTypes.node, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.object, }; -export default withStyles(styles, { name: 'MuiBreadcrumbs' })(Breadcrumbs); +export default Breadcrumbs; diff --git a/packages/material-ui/src/Breadcrumbs/Breadcrumbs.test.js b/packages/material-ui/src/Breadcrumbs/Breadcrumbs.test.js index 6403c057b898ab..acf662d0d9ae9f 100644 --- a/packages/material-ui/src/Breadcrumbs/Breadcrumbs.test.js +++ b/packages/material-ui/src/Breadcrumbs/Breadcrumbs.test.js @@ -1,34 +1,22 @@ import * as React from 'react'; import { expect } from 'chai'; -import { - getClasses, - createMount, - describeConformance, - act, - createClientRender, - screen, -} from 'test/utils'; +import { createMount, describeConformanceV5, act, createClientRender, screen } from 'test/utils'; import Breadcrumbs from './Breadcrumbs'; +import classes from './breadcrumbsClasses'; describe('', () => { const mount = createMount(); - let classes; const render = createClientRender(); - before(() => { - classes = getClasses( - - Hello World - , - ); - }); - - describeConformance(Conformance?, () => ({ + describeConformanceV5(Conformance?, () => ({ classes, inheritComponent: 'nav', mount, + muiName: 'MuiBreadcrumbs', refInstanceof: window.HTMLElement, testComponentPropWith: 'div', + testVariantProps: { separator: '=' }, + skip: ['componentsProp'], })); it('should render inaccessible separators between each listitem', () => { diff --git a/packages/material-ui/src/Breadcrumbs/breadcrumbCollapsedClasses.d.ts b/packages/material-ui/src/Breadcrumbs/breadcrumbCollapsedClasses.d.ts new file mode 100644 index 00000000000000..dfa4ccbb35dd6c --- /dev/null +++ b/packages/material-ui/src/Breadcrumbs/breadcrumbCollapsedClasses.d.ts @@ -0,0 +1,10 @@ +export interface BreadcrumbCollapsedClasses { + button: string; + icon: string; +} + +declare const breadcrumbCollapsedClasses: BreadcrumbCollapsedClasses; + +export function getBreadcrumbCollapsedUtilityClass(slot: string): string; + +export default breadcrumbCollapsedClasses; diff --git a/packages/material-ui/src/Breadcrumbs/breadcrumbCollapsedClasses.js b/packages/material-ui/src/Breadcrumbs/breadcrumbCollapsedClasses.js new file mode 100644 index 00000000000000..59c70590090fd2 --- /dev/null +++ b/packages/material-ui/src/Breadcrumbs/breadcrumbCollapsedClasses.js @@ -0,0 +1,12 @@ +import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled'; + +export function getBreadcrumbCollapsedUtilityClass(slot) { + return generateUtilityClass('PrivateBreadcrumbCollapsed', slot); +} + +const breadcrumbCollapsedClasses = generateUtilityClasses('PrivateBreadcrumbCollapsed', [ + 'button', + 'icon', +]); + +export default breadcrumbCollapsedClasses; diff --git a/packages/material-ui/src/Breadcrumbs/breadcrumbsClasses.d.ts b/packages/material-ui/src/Breadcrumbs/breadcrumbsClasses.d.ts new file mode 100644 index 00000000000000..db9038f12b0f70 --- /dev/null +++ b/packages/material-ui/src/Breadcrumbs/breadcrumbsClasses.d.ts @@ -0,0 +1,12 @@ +export interface BreadcrumbsClasses { + root: string; + ol: string; + li: string; + separator: string; +} + +declare const breadcrumbsClasses: BreadcrumbsClasses; + +export function getBreadcrumbsUtilityClass(slot: string): string; + +export default breadcrumbsClasses; diff --git a/packages/material-ui/src/Breadcrumbs/breadcrumbsClasses.js b/packages/material-ui/src/Breadcrumbs/breadcrumbsClasses.js new file mode 100644 index 00000000000000..c21d6bc1ea3539 --- /dev/null +++ b/packages/material-ui/src/Breadcrumbs/breadcrumbsClasses.js @@ -0,0 +1,14 @@ +import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled'; + +export function getBreadcrumbsUtilityClass(slot) { + return generateUtilityClass('MuiBreadcrumbs', slot); +} + +const breadcrumbsClasses = generateUtilityClasses('MuiBreadcrumbs', [ + 'root', + 'ol', + 'li', + 'separator', +]); + +export default breadcrumbsClasses; diff --git a/packages/material-ui/src/Breadcrumbs/index.d.ts b/packages/material-ui/src/Breadcrumbs/index.d.ts index d447c988bbcb58..fa0714c18f5187 100644 --- a/packages/material-ui/src/Breadcrumbs/index.d.ts +++ b/packages/material-ui/src/Breadcrumbs/index.d.ts @@ -1,2 +1,5 @@ export { default } from './Breadcrumbs'; export * from './Breadcrumbs'; + +export { default as breadcrumbsClasses } from './breadcrumbsClasses'; +export * from './breadcrumbsClasses'; diff --git a/packages/material-ui/src/Breadcrumbs/index.js b/packages/material-ui/src/Breadcrumbs/index.js index 3ff68ca58940e5..77829cbf764cab 100644 --- a/packages/material-ui/src/Breadcrumbs/index.js +++ b/packages/material-ui/src/Breadcrumbs/index.js @@ -1 +1,4 @@ export { default } from './Breadcrumbs'; + +export { default as breadcrumbsClasses } from './breadcrumbsClasses'; +export * from './breadcrumbsClasses';