UNPKG

19.3 kBJavaScriptView Raw
1import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
2import _extends from "@babel/runtime/helpers/esm/extends";
3const _excluded = ["className", "columns", "columnSpacing", "component", "container", "direction", "item", "rowSpacing", "spacing", "wrap", "zeroMinWidth"];
4// A grid component using the following libs as inspiration.
5//
6// For the implementation:
7// - https://getbootstrap.com/docs/4.3/layout/grid/
8// - https://github.com/kristoferjoseph/flexboxgrid/blob/master/src/css/flexboxgrid.css
9// - https://github.com/roylee0704/react-flexbox-grid
10// - https://material.angularjs.org/latest/layout/introduction
11//
12// Follow this flexbox Guide to better understand the underlying model:
13// - https://css-tricks.com/snippets/css/a-guide-to-flexbox/
14import * as React from 'react';
15import PropTypes from 'prop-types';
16import clsx from 'clsx';
17import { unstable_extendSxProp as extendSxProp, handleBreakpoints, unstable_resolveBreakpointValues as resolveBreakpointValues } from '@mui/system';
18import { unstable_composeClasses as composeClasses } from '@mui/base';
19import requirePropFactory from '../utils/requirePropFactory';
20import styled from '../styles/styled';
21import useThemeProps from '../styles/useThemeProps';
22import useTheme from '../styles/useTheme';
23import GridContext from './GridContext';
24import gridClasses, { getGridUtilityClass } from './gridClasses';
25import { jsx as _jsx } from "react/jsx-runtime";
26
27function getOffset(val) {
28 const parse = parseFloat(val);
29 return `${parse}${String(val).replace(String(parse), '') || 'px'}`;
30}
31
32export function generateGrid({
33 theme,
34 ownerState
35}) {
36 let size;
37 return theme.breakpoints.keys.reduce((globalStyles, breakpoint) => {
38 // Use side effect over immutability for better performance.
39 let styles = {};
40
41 if (ownerState[breakpoint]) {
42 size = ownerState[breakpoint];
43 }
44
45 if (!size) {
46 return globalStyles;
47 }
48
49 if (size === true) {
50 // For the auto layouting
51 styles = {
52 flexBasis: 0,
53 flexGrow: 1,
54 maxWidth: '100%'
55 };
56 } else if (size === 'auto') {
57 styles = {
58 flexBasis: 'auto',
59 flexGrow: 0,
60 flexShrink: 0,
61 maxWidth: 'none',
62 width: 'auto'
63 };
64 } else {
65 const columnsBreakpointValues = resolveBreakpointValues({
66 values: ownerState.columns,
67 breakpoints: theme.breakpoints.values
68 });
69 const columnValue = typeof columnsBreakpointValues === 'object' ? columnsBreakpointValues[breakpoint] : columnsBreakpointValues;
70
71 if (columnValue === undefined || columnValue === null) {
72 return globalStyles;
73 } // Keep 7 significant numbers.
74
75
76 const width = `${Math.round(size / columnValue * 10e7) / 10e5}%`;
77 let more = {};
78
79 if (ownerState.container && ownerState.item && ownerState.columnSpacing !== 0) {
80 const themeSpacing = theme.spacing(ownerState.columnSpacing);
81
82 if (themeSpacing !== '0px') {
83 const fullWidth = `calc(${width} + ${getOffset(themeSpacing)})`;
84 more = {
85 flexBasis: fullWidth,
86 maxWidth: fullWidth
87 };
88 }
89 } // Close to the bootstrap implementation:
90 // https://github.com/twbs/bootstrap/blob/8fccaa2439e97ec72a4b7dc42ccc1f649790adb0/scss/mixins/_grid.scss#L41
91
92
93 styles = _extends({
94 flexBasis: width,
95 flexGrow: 0,
96 maxWidth: width
97 }, more);
98 } // No need for a media query for the first size.
99
100
101 if (theme.breakpoints.values[breakpoint] === 0) {
102 Object.assign(globalStyles, styles);
103 } else {
104 globalStyles[theme.breakpoints.up(breakpoint)] = styles;
105 }
106
107 return globalStyles;
108 }, {});
109}
110export function generateDirection({
111 theme,
112 ownerState
113}) {
114 const directionValues = resolveBreakpointValues({
115 values: ownerState.direction,
116 breakpoints: theme.breakpoints.values
117 });
118 return handleBreakpoints({
119 theme
120 }, directionValues, propValue => {
121 const output = {
122 flexDirection: propValue
123 };
124
125 if (propValue.indexOf('column') === 0) {
126 output[`& > .${gridClasses.item}`] = {
127 maxWidth: 'none'
128 };
129 }
130
131 return output;
132 });
133}
134/**
135 * Extracts zero value breakpoint keys before a non-zero value breakpoint key.
136 * @example { xs: 0, sm: 0, md: 2, lg: 0, xl: 0 } or [0, 0, 2, 0, 0]
137 * @returns [xs, sm]
138 */
139
140function extractZeroValueBreakpointKeys({
141 breakpoints,
142 values
143}) {
144 let nonZeroKey = '';
145 Object.keys(values).forEach(key => {
146 if (nonZeroKey !== '') {
147 return;
148 }
149
150 if (values[key] !== 0) {
151 nonZeroKey = key;
152 }
153 });
154 const sortedBreakpointKeysByValue = Object.keys(breakpoints).sort((a, b) => {
155 return breakpoints[a] - breakpoints[b];
156 });
157 return sortedBreakpointKeysByValue.slice(0, sortedBreakpointKeysByValue.indexOf(nonZeroKey));
158}
159
160export function generateRowGap({
161 theme,
162 ownerState
163}) {
164 const {
165 container,
166 rowSpacing
167 } = ownerState;
168 let styles = {};
169
170 if (container && rowSpacing !== 0) {
171 const rowSpacingValues = resolveBreakpointValues({
172 values: rowSpacing,
173 breakpoints: theme.breakpoints.values
174 });
175 let zeroValueBreakpointKeys;
176
177 if (typeof rowSpacingValues === 'object') {
178 zeroValueBreakpointKeys = extractZeroValueBreakpointKeys({
179 breakpoints: theme.breakpoints.values,
180 values: rowSpacingValues
181 });
182 }
183
184 styles = handleBreakpoints({
185 theme
186 }, rowSpacingValues, (propValue, breakpoint) => {
187 const themeSpacing = theme.spacing(propValue);
188
189 if (themeSpacing !== '0px') {
190 return {
191 marginTop: `-${getOffset(themeSpacing)}`,
192 [`& > .${gridClasses.item}`]: {
193 paddingTop: getOffset(themeSpacing)
194 }
195 };
196 }
197
198 if (zeroValueBreakpointKeys?.includes(breakpoint)) {
199 return {};
200 }
201
202 return {
203 marginTop: 0,
204 [`& > .${gridClasses.item}`]: {
205 paddingTop: 0
206 }
207 };
208 });
209 }
210
211 return styles;
212}
213export function generateColumnGap({
214 theme,
215 ownerState
216}) {
217 const {
218 container,
219 columnSpacing
220 } = ownerState;
221 let styles = {};
222
223 if (container && columnSpacing !== 0) {
224 const columnSpacingValues = resolveBreakpointValues({
225 values: columnSpacing,
226 breakpoints: theme.breakpoints.values
227 });
228 let zeroValueBreakpointKeys;
229
230 if (typeof columnSpacingValues === 'object') {
231 zeroValueBreakpointKeys = extractZeroValueBreakpointKeys({
232 breakpoints: theme.breakpoints.values,
233 values: columnSpacingValues
234 });
235 }
236
237 styles = handleBreakpoints({
238 theme
239 }, columnSpacingValues, (propValue, breakpoint) => {
240 const themeSpacing = theme.spacing(propValue);
241
242 if (themeSpacing !== '0px') {
243 return {
244 width: `calc(100% + ${getOffset(themeSpacing)})`,
245 marginLeft: `-${getOffset(themeSpacing)}`,
246 [`& > .${gridClasses.item}`]: {
247 paddingLeft: getOffset(themeSpacing)
248 }
249 };
250 }
251
252 if (zeroValueBreakpointKeys?.includes(breakpoint)) {
253 return {};
254 }
255
256 return {
257 width: '100%',
258 marginLeft: 0,
259 [`& > .${gridClasses.item}`]: {
260 paddingLeft: 0
261 }
262 };
263 });
264 }
265
266 return styles;
267}
268export function resolveSpacingStyles(spacing, breakpoints, styles = {}) {
269 // undefined/null or `spacing` <= 0
270 if (!spacing || spacing <= 0) {
271 return [];
272 } // in case of string/number `spacing`
273
274
275 if (typeof spacing === 'string' && !Number.isNaN(Number(spacing)) || typeof spacing === 'number') {
276 return [styles[`spacing-xs-${String(spacing)}`]];
277 } // in case of object `spacing`
278
279
280 const spacingStyles = [];
281 breakpoints.forEach(breakpoint => {
282 const value = spacing[breakpoint];
283
284 if (Number(value) > 0) {
285 spacingStyles.push(styles[`spacing-${breakpoint}-${String(value)}`]);
286 }
287 });
288 return spacingStyles;
289} // Default CSS values
290// flex: '0 1 auto',
291// flexDirection: 'row',
292// alignItems: 'flex-start',
293// flexWrap: 'nowrap',
294// justifyContent: 'flex-start',
295
296const GridRoot = styled('div', {
297 name: 'MuiGrid',
298 slot: 'Root',
299 overridesResolver: (props, styles) => {
300 const {
301 ownerState
302 } = props;
303 const {
304 container,
305 direction,
306 item,
307 spacing,
308 wrap,
309 zeroMinWidth,
310 breakpoints
311 } = ownerState;
312 let spacingStyles = []; // in case of grid item
313
314 if (container) {
315 spacingStyles = resolveSpacingStyles(spacing, breakpoints, styles);
316 }
317
318 const breakpointsStyles = [];
319 breakpoints.forEach(breakpoint => {
320 const value = ownerState[breakpoint];
321
322 if (value) {
323 breakpointsStyles.push(styles[`grid-${breakpoint}-${String(value)}`]);
324 }
325 });
326 return [styles.root, container && styles.container, item && styles.item, zeroMinWidth && styles.zeroMinWidth, ...spacingStyles, direction !== 'row' && styles[`direction-xs-${String(direction)}`], wrap !== 'wrap' && styles[`wrap-xs-${String(wrap)}`], ...breakpointsStyles];
327 }
328})(({
329 ownerState
330}) => _extends({
331 boxSizing: 'border-box'
332}, ownerState.container && {
333 display: 'flex',
334 flexWrap: 'wrap',
335 width: '100%'
336}, ownerState.item && {
337 margin: 0 // For instance, it's useful when used with a `figure` element.
338
339}, ownerState.zeroMinWidth && {
340 minWidth: 0
341}, ownerState.wrap !== 'wrap' && {
342 flexWrap: ownerState.wrap
343}), generateDirection, generateRowGap, generateColumnGap, generateGrid);
344export function resolveSpacingClasses(spacing, breakpoints) {
345 // undefined/null or `spacing` <= 0
346 if (!spacing || spacing <= 0) {
347 return [];
348 } // in case of string/number `spacing`
349
350
351 if (typeof spacing === 'string' && !Number.isNaN(Number(spacing)) || typeof spacing === 'number') {
352 return [`spacing-xs-${String(spacing)}`];
353 } // in case of object `spacing`
354
355
356 const classes = [];
357 breakpoints.forEach(breakpoint => {
358 const value = spacing[breakpoint];
359
360 if (Number(value) > 0) {
361 const className = `spacing-${breakpoint}-${String(value)}`;
362 classes.push(className);
363 }
364 });
365 return classes;
366}
367
368const useUtilityClasses = ownerState => {
369 const {
370 classes,
371 container,
372 direction,
373 item,
374 spacing,
375 wrap,
376 zeroMinWidth,
377 breakpoints
378 } = ownerState;
379 let spacingClasses = []; // in case of grid item
380
381 if (container) {
382 spacingClasses = resolveSpacingClasses(spacing, breakpoints);
383 }
384
385 const breakpointsClasses = [];
386 breakpoints.forEach(breakpoint => {
387 const value = ownerState[breakpoint];
388
389 if (value) {
390 breakpointsClasses.push(`grid-${breakpoint}-${String(value)}`);
391 }
392 });
393 const slots = {
394 root: ['root', container && 'container', item && 'item', zeroMinWidth && 'zeroMinWidth', ...spacingClasses, direction !== 'row' && `direction-xs-${String(direction)}`, wrap !== 'wrap' && `wrap-xs-${String(wrap)}`, ...breakpointsClasses]
395 };
396 return composeClasses(slots, getGridUtilityClass, classes);
397};
398
399const Grid = /*#__PURE__*/React.forwardRef(function Grid(inProps, ref) {
400 const themeProps = useThemeProps({
401 props: inProps,
402 name: 'MuiGrid'
403 });
404 const {
405 breakpoints
406 } = useTheme();
407 const props = extendSxProp(themeProps);
408
409 const {
410 className,
411 columns: columnsProp,
412 columnSpacing: columnSpacingProp,
413 component = 'div',
414 container = false,
415 direction = 'row',
416 item = false,
417 rowSpacing: rowSpacingProp,
418 spacing = 0,
419 wrap = 'wrap',
420 zeroMinWidth = false
421 } = props,
422 other = _objectWithoutPropertiesLoose(props, _excluded);
423
424 const rowSpacing = rowSpacingProp || spacing;
425 const columnSpacing = columnSpacingProp || spacing;
426 const columnsContext = React.useContext(GridContext); // columns set with default breakpoint unit of 12
427
428 const columns = container ? columnsProp || 12 : columnsContext;
429 const breakpointsValues = {};
430
431 const otherFiltered = _extends({}, other);
432
433 breakpoints.keys.forEach(breakpoint => {
434 if (other[breakpoint] != null) {
435 breakpointsValues[breakpoint] = other[breakpoint];
436 delete otherFiltered[breakpoint];
437 }
438 });
439
440 const ownerState = _extends({}, props, {
441 columns,
442 container,
443 direction,
444 item,
445 rowSpacing,
446 columnSpacing,
447 wrap,
448 zeroMinWidth,
449 spacing
450 }, breakpointsValues, {
451 breakpoints: breakpoints.keys
452 });
453
454 const classes = useUtilityClasses(ownerState);
455 return /*#__PURE__*/_jsx(GridContext.Provider, {
456 value: columns,
457 children: /*#__PURE__*/_jsx(GridRoot, _extends({
458 ownerState: ownerState,
459 className: clsx(classes.root, className),
460 as: component,
461 ref: ref
462 }, otherFiltered))
463 });
464});
465process.env.NODE_ENV !== "production" ? Grid.propTypes
466/* remove-proptypes */
467= {
468 // ----------------------------- Warning --------------------------------
469 // | These PropTypes are generated from the TypeScript type definitions |
470 // | To update them edit the d.ts file and run "yarn proptypes" |
471 // ----------------------------------------------------------------------
472
473 /**
474 * The content of the component.
475 */
476 children: PropTypes.node,
477
478 /**
479 * Override or extend the styles applied to the component.
480 */
481 classes: PropTypes.object,
482
483 /**
484 * @ignore
485 */
486 className: PropTypes.string,
487
488 /**
489 * The number of columns.
490 * @default 12
491 */
492 columns: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number, PropTypes.object]),
493
494 /**
495 * Defines the horizontal space between the type `item` components.
496 * It overrides the value of the `spacing` prop.
497 */
498 columnSpacing: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), PropTypes.number, PropTypes.object, PropTypes.string]),
499
500 /**
501 * The component used for the root node.
502 * Either a string to use a HTML element or a component.
503 */
504 component: PropTypes.elementType,
505
506 /**
507 * If `true`, the component will have the flex *container* behavior.
508 * You should be wrapping *items* with a *container*.
509 * @default false
510 */
511 container: PropTypes.bool,
512
513 /**
514 * Defines the `flex-direction` style property.
515 * It is applied for all screen sizes.
516 * @default 'row'
517 */
518 direction: PropTypes.oneOfType([PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row']), PropTypes.arrayOf(PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row'])), PropTypes.object]),
519
520 /**
521 * If `true`, the component will have the flex *item* behavior.
522 * You should be wrapping *items* with a *container*.
523 * @default false
524 */
525 item: PropTypes.bool,
526
527 /**
528 * If a number, it sets the number of columns the grid item uses.
529 * It can't be greater than the total number of columns of the container (12 by default).
530 * If 'auto', the grid item's width matches its content.
531 * If false, the prop is ignored.
532 * If true, the grid item's width grows to use the space available in the grid container.
533 * The value is applied for the `lg` breakpoint and wider screens if not overridden.
534 * @default false
535 */
536 lg: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
537
538 /**
539 * If a number, it sets the number of columns the grid item uses.
540 * It can't be greater than the total number of columns of the container (12 by default).
541 * If 'auto', the grid item's width matches its content.
542 * If false, the prop is ignored.
543 * If true, the grid item's width grows to use the space available in the grid container.
544 * The value is applied for the `md` breakpoint and wider screens if not overridden.
545 * @default false
546 */
547 md: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
548
549 /**
550 * Defines the vertical space between the type `item` components.
551 * It overrides the value of the `spacing` prop.
552 */
553 rowSpacing: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), PropTypes.number, PropTypes.object, PropTypes.string]),
554
555 /**
556 * If a number, it sets the number of columns the grid item uses.
557 * It can't be greater than the total number of columns of the container (12 by default).
558 * If 'auto', the grid item's width matches its content.
559 * If false, the prop is ignored.
560 * If true, the grid item's width grows to use the space available in the grid container.
561 * The value is applied for the `sm` breakpoint and wider screens if not overridden.
562 * @default false
563 */
564 sm: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
565
566 /**
567 * Defines the space between the type `item` components.
568 * It can only be used on a type `container` component.
569 * @default 0
570 */
571 spacing: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), PropTypes.number, PropTypes.object, PropTypes.string]),
572
573 /**
574 * The system prop that allows defining system overrides as well as additional CSS styles.
575 */
576 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
577
578 /**
579 * Defines the `flex-wrap` style property.
580 * It's applied for all screen sizes.
581 * @default 'wrap'
582 */
583 wrap: PropTypes.oneOf(['nowrap', 'wrap-reverse', 'wrap']),
584
585 /**
586 * If a number, it sets the number of columns the grid item uses.
587 * It can't be greater than the total number of columns of the container (12 by default).
588 * If 'auto', the grid item's width matches its content.
589 * If false, the prop is ignored.
590 * If true, the grid item's width grows to use the space available in the grid container.
591 * The value is applied for the `xl` breakpoint and wider screens if not overridden.
592 * @default false
593 */
594 xl: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
595
596 /**
597 * If a number, it sets the number of columns the grid item uses.
598 * It can't be greater than the total number of columns of the container (12 by default).
599 * If 'auto', the grid item's width matches its content.
600 * If false, the prop is ignored.
601 * If true, the grid item's width grows to use the space available in the grid container.
602 * The value is applied for all the screen sizes with the lowest priority.
603 * @default false
604 */
605 xs: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
606
607 /**
608 * If `true`, it sets `min-width: 0` on the item.
609 * Refer to the limitations section of the documentation to better understand the use case.
610 * @default false
611 */
612 zeroMinWidth: PropTypes.bool
613} : void 0;
614
615if (process.env.NODE_ENV !== 'production') {
616 const requireProp = requirePropFactory('Grid', Grid); // eslint-disable-next-line no-useless-concat
617
618 Grid['propTypes' + ''] = _extends({}, Grid.propTypes, {
619 direction: requireProp('container'),
620 lg: requireProp('item'),
621 md: requireProp('item'),
622 sm: requireProp('item'),
623 spacing: requireProp('container'),
624 wrap: requireProp('container'),
625 xs: requireProp('item'),
626 zeroMinWidth: requireProp('item')
627 });
628}
629
630export default Grid;
\No newline at end of file