Skip to content

Commit

Permalink
[docs] Add density guide to customizations (#16410)
Browse files Browse the repository at this point in the history
* [docs] Use dense theme

* Reduce button size

* Reach 48dp touch hit target

* Add merge strategy for theme decorators

* [Button] Default to small in a dense theme

* [FilledInput] Use dense margin in dense theme

* [FormControl] Use dense margin in a dense theme

* [FormHelperText] Use dense margin in a dense theme

* [Fab] Use small size in a dense theme

* [IconButton] Use small size in a dense theme

* [InputBase] Use dense margin in a dense theme

* [InputLabel] Use dense margin in a dense theme

* [ListItem] Use dense in a dense theme

* [OutlinedInput] Use dense margin in a dense theme

* [Table] Use small size in a dense theme

* [Toolbar] Use dense variant in a dense theme

* [docs] Update dense theme

* [docs] Update API docs regarding density

* Fancy dense switch [skip ci]

* New draft for density guide

* Test that density priority is props > context > theme

* Exclude density customization

* Remove theme.dense

* Apply suggestions from Matt's code review

Co-Authored-By: Matt <github@nospam.33m.co>

* Apply suggestions from code review

Co-Authored-By: Matt <github@nospam.33m.co>
  • Loading branch information
eps1lon and mbrookes authored Jul 9, 2019
1 parent 7d826a9 commit 485916a
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 27 deletions.
2 changes: 1 addition & 1 deletion docs/src/modules/components/AppWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
150 changes: 124 additions & 26 deletions docs/src/modules/components/ThemeContext.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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';
Expand All @@ -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.
Expand All @@ -108,13 +206,13 @@ export function Provider(props) {
}, [theme]);

return (
<ThemeProvider theme={theme}>
<MuiThemeProvider theme={theme}>
<DispatchContext.Provider value={dispatch}>{children}</DispatchContext.Provider>
</ThemeProvider>
</MuiThemeProvider>
);
}

Provider.propTypes = {
ThemeProvider.propTypes = {
children: PropTypes.node,
};

Expand Down
1 change: 1 addition & 0 deletions docs/src/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ const pages = [
{ pathname: '/customization/typography' },
{ pathname: '/customization/spacing' },
{ pathname: '/customization/breakpoints' },
{ pathname: '/customization/density' },
{ pathname: '/customization/z-index', title: 'z-index' },
{ pathname: '/customization/globals' },
],
Expand Down
94 changes: 94 additions & 0 deletions docs/src/pages/customization/density/DensityTool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import { useTheme } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Input from '@material-ui/core/Input';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import { DispatchContext } from 'docs/src/modules/components/ThemeContext';
import IncreaseIcon from '@material-ui/icons/AddCircleOutline';
import DecreaseIcon from '@material-ui/icons/RemoveCircleOutline';
import { useSelector } from 'react-redux';

const minSpacing = 0;
const maxSpacing = 20;

export default function DensityTool() {
const dispatch = React.useContext(DispatchContext);
function handleDensityChange(event) {
dispatch({ type: 'SET_DENSE', payload: event.target.checked });
}

function handleSpacingChange(event, value) {
dispatch({ type: 'SET_SPACING', payload: value || +event.target.value });
}

function increaseSpacing() {
dispatch({ type: 'INCREASE_SPACING' });
}

function decreaseSpacing() {
dispatch({ type: 'DECREASE_SPACING' });
}

function resetDensity() {
dispatch({ type: 'RESET_DENSITY' });
}

const theme = useTheme();
const spacingUnit = theme.spacing(1);

const t = useSelector(state => state.options.t);

return (
<Grid container spacing={2}>
<Grid container item>
<FormControlLabel
control={
<Switch
checked={theme.dense}
onChange={handleDensityChange}
value="dense"
color="secondary"
/>
}
label={t('useHighDensity')}
/>
</Grid>
<Grid container item alignItems="center" spacing={2}>
<Grid item>
<Typography id="input-slider" gutterBottom>
{t('spacingUnit')}
</Typography>
</Grid>
<Grid item>
<IconButton aria-label={t('increaseSpacing')} onClick={decreaseSpacing}>
<DecreaseIcon />
</IconButton>
<Input
value={spacingUnit}
margin="dense"
onChange={handleSpacingChange}
inputProps={{
step: 1,
min: minSpacing,
max: maxSpacing,
type: 'number',
'aria-labelledby': 'input-slider',
}}
/>
<IconButton aria-label={t('decreaseSpacing')} onClick={increaseSpacing}>
<IncreaseIcon />
</IconButton>
</Grid>
</Grid>
<Grid item>
<Button color="primary" variant="contained" onClick={resetDensity}>
{t('resetDensity')}
</Button>
</Grid>
</Grid>
);
}
Loading

0 comments on commit 485916a

Please sign in to comment.