diff --git a/docs/src/modules/components/AppWrapper.js b/docs/src/modules/components/AppWrapper.js
index 4d5f73f7940ad2..4b438d01e1c1b6 100644
--- a/docs/src/modules/components/AppWrapper.js
+++ b/docs/src/modules/components/AppWrapper.js
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { StylesProvider, jssPreset } from '@material-ui/styles';
-import { Provider as ThemeProvider } from 'docs/src/modules/components/ThemeContext';
+import { ThemeProvider } from 'docs/src/modules/components/ThemeContext';
import { getCookie } from 'docs/src/modules/utils/helpers';
import { ACTION_TYPES, CODE_VARIANTS } from 'docs/src/modules/constants';
import { create } from 'jss';
diff --git a/docs/src/modules/components/ThemeContext.js b/docs/src/modules/components/ThemeContext.js
index c9732b63a12fe5..8b3ac2c9565805 100644
--- a/docs/src/modules/components/ThemeContext.js
+++ b/docs/src/modules/components/ThemeContext.js
@@ -1,37 +1,131 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { ThemeProvider } from '@material-ui/styles';
+import { ThemeProvider as MuiThemeProvider } from '@material-ui/styles';
import { createMuiTheme, darken } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { blue, pink } from '@material-ui/core/colors';
import { getCookie } from 'docs/src/modules/utils/helpers';
import { darkTheme, setPrismTheme } from 'docs/src/modules/components/prism';
+import deepmerge from 'deepmerge';
export const themeColor = blue[700];
const themeInitialOptions = {
+ dense: false,
direction: 'ltr',
paletteColors: {},
+ spacing: 8, // spacing unit
};
+/**
+ * @typedef {import('@material-ui/core/src/styles/createMuiTheme').ThemeOptions} ThemeOptions
+ *
+ *
+ * @param {ThemeOptions} themeOptions
+ * @returns {ThemeOptions}
+ */
+function usingHighDensity(themeOptions) {
+ return deepmerge(themeOptions, {
+ props: {
+ MuiButton: {
+ size: 'small',
+ },
+ MuiFilledInput: {
+ margin: 'dense',
+ },
+ MuiFormControl: {
+ margin: 'dense',
+ },
+ MuiFormHelperText: {
+ margin: 'dense',
+ },
+ MuiIconButton: {
+ size: 'small',
+ },
+ MuiInputBase: {
+ margin: 'dense',
+ },
+ MuiInputLabel: {
+ margin: 'dense',
+ },
+ MuiListItem: {
+ dense: true,
+ },
+ MuiOutlinedInput: {
+ margin: 'dense',
+ },
+ MuiFab: {
+ size: 'small',
+ },
+ MuiTable: {
+ size: 'small',
+ },
+ MuiTextField: {
+ margin: 'dense',
+ },
+ MuiToolbar: {
+ variant: 'dense',
+ },
+ },
+ overrides: {
+ MuiIconButton: {
+ sizeSmall: {
+ // minimal touch target hit spacing
+ marginLeft: 4,
+ marginRight: 4,
+ padding: 12,
+ },
+ },
+ },
+ });
+}
+
+function usingIdentity(themeOptions) {
+ return themeOptions;
+}
+
export const DispatchContext = React.createContext(() => {
throw new Error('Forgot to wrap component in ThemeContext.Provider');
});
const useEnhancedEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
-export function Provider(props) {
+export function ThemeProvider(props) {
const { children } = props;
const [themeOptions, dispatch] = React.useReducer((state, action) => {
switch (action.type) {
- case 'RESET_COLORS':
+ case 'SET_SPACING':
+ return {
+ ...state,
+ spacing: action.payload,
+ };
+ case 'INCREASE_SPACING': {
+ return {
+ ...state,
+ spacing: state.spacing + 1,
+ };
+ }
+ case 'DECREASE_SPACING': {
+ return {
+ ...state,
+ spacing: state.spacing - 1,
+ };
+ }
+ case 'SET_DENSE':
+ return {
+ ...state,
+ dense: action.payload,
+ };
+ case 'RESET_DENSITY':
return {
...state,
- paletteColors: themeInitialOptions.paletteColors,
+ dense: themeInitialOptions.dense,
+ spacing: themeInitialOptions.spacing,
};
case 'CHANGE':
return {
+ ...state,
paletteType: action.payload.paletteType || state.paletteType,
direction: action.payload.direction || state.direction,
paletteColors: action.payload.paletteColors || state.paletteColors,
@@ -43,7 +137,7 @@ export function Provider(props) {
const prefersDarkMode = useMediaQuery('@media (prefers-color-scheme: dark)');
const preferredType = prefersDarkMode ? 'dark' : 'light';
- const { direction, paletteColors, paletteType = preferredType } = themeOptions;
+ const { dense, direction, paletteColors, paletteType = preferredType, spacing } = themeOptions;
React.useEffect(() => {
setPrismTheme(darkTheme);
@@ -71,25 +165,29 @@ export function Provider(props) {
}, [direction]);
const theme = React.useMemo(() => {
- const nextTheme = createMuiTheme({
- direction,
- nprogress: {
- color: paletteType === 'light' ? '#000' : '#fff',
- },
- palette: {
- primary: {
- main: paletteType === 'light' ? blue[700] : blue[200],
+ const themeDecorator = dense ? usingHighDensity : usingIdentity;
+ const nextTheme = createMuiTheme(
+ themeDecorator({
+ direction,
+ nprogress: {
+ color: paletteType === 'light' ? '#000' : '#fff',
},
- secondary: {
- main: paletteType === 'light' ? darken(pink.A400, 0.1) : pink[200],
+ palette: {
+ primary: {
+ main: paletteType === 'light' ? blue[700] : blue[200],
+ },
+ secondary: {
+ main: paletteType === 'light' ? darken(pink.A400, 0.1) : pink[200],
+ },
+ type: paletteType,
+ background: {
+ default: paletteType === 'light' ? '#fff' : '#121212',
+ },
+ ...paletteColors,
},
- type: paletteType,
- background: {
- default: paletteType === 'light' ? '#fff' : '#121212',
- },
- ...paletteColors,
- },
- });
+ spacing,
+ }),
+ );
nextTheme.palette.background.level2 =
paletteType === 'light' ? nextTheme.palette.grey[100] : '#333';
@@ -98,7 +196,7 @@ export function Provider(props) {
paletteType === 'light' ? '#fff' : nextTheme.palette.grey[900];
return nextTheme;
- }, [direction, paletteColors, paletteType]);
+ }, [dense, direction, paletteColors, paletteType, spacing]);
React.useEffect(() => {
// Expose the theme as a global variable so people can play with it.
@@ -108,13 +206,13 @@ export function Provider(props) {
}, [theme]);
return (
-
How to apply density to Material-UI components.
+ +## Applying density + +We won't cover possible use cases, or considerations for using density in your application. +The Material design guidelines have a [comprehensive guide](https://material.io/design/layout/applying-density.html#typographic-density) covering these topics in more detail. + +## Implementing density + +Higher density can be applied to some components via props. The component pages +have at least one example using the respective component with higher density applied. + +Depending on the component, density is applied either via lower spacing, or simply by +reducing the size. + +The following components have props applying higher density: + +- [Button](/api/button) +- [Fab](/api/fab) +- [FilledInput](/api/filled-input) +- [FormControl](/api/form-control) +- [FormHelperText](/api/form-helper-text) +- [IconButton](/api/icon-button) +- [InputBase](/api/input-base) +- [InputLabel](/api/input-label) +- [ListItem](/api/list-item) +- [OutlinedInput](/api/outlined-input) +- [Table](/api/table) +- [TextField](/api/text-field) +- [Toolbar](/api/toolbar) + +## Explore theme density + +This tool allows you to apply density via spacing and component props. You can browse +around and see how this applies to the overall feel of Material-UI components. + +If you enable high density a custom theme is applied to the docs. This theme is only +for demonstration purposes. You _should not_ apply this theme to your whole application +as this might negatively impact user experience. The [Material design guidelines](https://material.io/design/layout/applying-density.html#typographic-density) has examples +for when not to apply density. + +The theme is configured with the following options: + +```js +const theme = createMuiTheme({ + props: { + MuiButton: { + size: 'small', + }, + MuiFilledInput: { + margin: 'dense', + }, + MuiFormControl: { + margin: 'dense', + }, + MuiFormHelperText: { + margin: 'dense', + }, + MuiIconButton: { + size: 'small', + }, + MuiInputBase: { + margin: 'dense', + }, + MuiInputLabel: { + margin: 'dense', + }, + MuiListItem: { + dense: true, + }, + MuiOutlinedInput: { + margin: 'dense', + }, + MuiFab: { + size: 'small', + }, + MuiTable: { + size: 'small', + }, + MuiTextField: { + margin: 'dense', + }, + MuiToolbar: { + variant: 'dense', + }, + }, + overrides: { + MuiIconButton: { + sizeSmall: { + // Adjust spacing to reach minimal touch target hitbox + marginLeft: 4, + marginRight: 4, + padding: 12, + }, + }, + }, +}); +``` + +{{"demo": "pages/customization/density/DensityTool.js", "hideHeader": true}} diff --git a/docs/translations/translations.json b/docs/translations/translations.json index a44346fd3c0a92..26b706d1055008 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -77,6 +77,11 @@ "showJSSource": "Show JavaScript source", "showTSSource": "Show TypeScript source", "close": "Close", + "useHighDensity": "Apply higher density via props", + "spacingUnit": "Spacing unit", + "resetDensity": "Reset density", + "increaseSpacing": "increase spacing", + "decreaseSpacing": "decrease spacing", "pages": { "/getting-started": "Getting Started", "/getting-started/installation": "Installation", diff --git a/packages/material-ui/src/InputLabel/InputLabel.d.ts b/packages/material-ui/src/InputLabel/InputLabel.d.ts index 7cb5088c418c4b..4fd58506110278 100644 --- a/packages/material-ui/src/InputLabel/InputLabel.d.ts +++ b/packages/material-ui/src/InputLabel/InputLabel.d.ts @@ -7,6 +7,7 @@ export interface InputLabelProps extends StandardProps