UNPKG

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