diff --git a/.eslintrc b/.eslintrc
index a4fc0a5f1..5f4b16604 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -25,7 +25,8 @@
"rules": {
"react/jsx-filename-extension": [1, { "extensions": [".mdx"] }],
"react/jsx-indent": "off", // Gives false positives in MDX files.
- "semi": "off" // We don't want to clutter our MDX with semicolons.
+ "semi": "off", // We don't want to clutter our MDX with semicolons.
+ "sort-keys": "off" // This rule only needs to be turned off in a few places (eg. breakpoint lists), but weirdly enough, an inline comment alerts breaking of the `indent` rule.
}
}
]
diff --git a/package-lock.json b/package-lock.json
index 4e3228dd2..6d37e3aa0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8170,11 +8170,6 @@
"postcss-value-parser": "^4.1.0"
},
"dependencies": {
- "caniuse-lite": {
- "version": "1.0.30001112",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001112.tgz",
- "integrity": "sha512-J05RTQlqsatidif/38aN3PGULCLrg8OYQOlJUKbeYVzC2mGZkZLIztwRlB3MtrfLmawUmjFlNJvy/uhwniIe1Q=="
- },
"postcss": {
"version": "7.0.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
@@ -9734,9 +9729,9 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001058",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001058.tgz",
- "integrity": "sha512-UiRZmBYd1HdVVdFKy7PuLVx9e2NS7SMyx7QpWvFjiklYrLJKpLd19cRnRNqlw4zYa7vVejS3c8JUVobX241zHQ=="
+ "version": "1.0.30001154",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001154.tgz",
+ "integrity": "sha512-y9DvdSti8NnYB9Be92ddMZQrcOe04kcQtcxtBx4NkB04+qZ+JUWotnXBJTmxlKudhxNTQ3RRknMwNU2YQl/Org=="
},
"capitalize": {
"version": "2.0.3",
@@ -17097,11 +17092,6 @@
}
}
},
- "caniuse-lite": {
- "version": "1.0.30001135",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001135.tgz",
- "integrity": "sha512-ziNcheTGTHlu9g34EVoHQdIu5g4foc8EsxMGC7Xkokmvw0dqNtX8BS8RgCgFBaAiSp2IdjvBxNdh0ssib28eVQ=="
- },
"chokidar": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
diff --git a/src/gatsby-theme-docz/wrapper.js b/src/gatsby-theme-docz/wrapper.js
index 935a5cb5e..158c95b19 100644
--- a/src/gatsby-theme-docz/wrapper.js
+++ b/src/gatsby-theme-docz/wrapper.js
@@ -23,6 +23,15 @@ const Wrapper = ({ children }) => (
gtag('config', 'UA-173070814-1');
`}
+
{children}
>
diff --git a/src/lib/components/layout/CTA/README.mdx b/src/lib/components/layout/CTA/README.mdx
index 5ee07d174..66080351a 100644
--- a/src/lib/components/layout/CTA/README.mdx
+++ b/src/lib/components/layout/CTA/README.mdx
@@ -100,19 +100,19 @@ and bottom.
-## CTAStart
+### CTAStart
The start element of the CTA layout.
-## CTACenter
+### CTACenter
The center element of the CTA layout.
-## CTAEnd
+### CTAEnd
The end element of the CTA layout.
diff --git a/src/lib/components/layout/FormLayout/FormLayout.jsx b/src/lib/components/layout/FormLayout/FormLayout.jsx
index 736fafafc..cd84623c7 100644
--- a/src/lib/components/layout/FormLayout/FormLayout.jsx
+++ b/src/lib/components/layout/FormLayout/FormLayout.jsx
@@ -55,6 +55,10 @@ export const FormLayout = (props) => {
].join(' ')}
{...hasCustomLabelWidth ? { style: { '--rui-custom-label-width': labelWidth } } : {}}
>
+ {/*
+ Flatten children to one-dimensional array so we get over React Fragments with the `map()`
+ function.
+ */}
{flattenChildren(children).map((child) => {
if (!React.isValidElement(child)) {
return null;
diff --git a/src/lib/components/layout/FormLayout/README.mdx b/src/lib/components/layout/FormLayout/README.mdx
index d5f3750de..1ff8de089 100644
--- a/src/lib/components/layout/FormLayout/README.mdx
+++ b/src/lib/components/layout/FormLayout/README.mdx
@@ -367,7 +367,7 @@ This is a demo of all components supported by FormLayout.
-## FormLayoutCustomField
+### FormLayoutCustomField
A place for custom content inside FormLayout.
diff --git a/src/lib/components/layout/Grid/Grid.jsx b/src/lib/components/layout/Grid/Grid.jsx
index c802fda38..b529f75d2 100644
--- a/src/lib/components/layout/Grid/Grid.jsx
+++ b/src/lib/components/layout/Grid/Grid.jsx
@@ -1,16 +1,19 @@
-import flattenChildren from 'react-keyed-flatten-children';
import PropTypes from 'prop-types';
import React from 'react';
+import { generateResponsiveCustomProperties } from './helpers/generateResponsiveCustomProperties';
import styles from './Grid.scss';
-export const Grid = (props) => {
- const {
- children,
- id,
- ...other
- } = props;
-
- if (!props.children) {
+export const Grid = ({
+ autoFlow,
+ children,
+ columnGap,
+ columns,
+ id,
+ rowGap,
+ rows,
+ ...other
+}) => {
+ if (!children) {
return null;
}
@@ -18,33 +21,111 @@ export const Grid = (props) => {
- {flattenChildren(children).map((child) => {
- if (!React.isValidElement(child)) {
- return null;
- }
-
- return React.cloneElement(child);
- })}
+ {children}
);
};
+/* Breakpoints are easier to work with when ordered according to their value, not name. */
+/* eslint-disable sort-keys */
+
Grid.defaultProps = {
children: null,
+ columnGap: undefined,
+ columns: undefined,
+ autoFlow: undefined,
id: undefined,
+ rowGap: undefined,
+ rows: undefined,
};
Grid.propTypes = {
+ /**
+ * Grid auto-flow algorithm to be used. Accepts any valid value of `grid-auto-flow` CSS property.
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow) for more.
+ */
+ autoFlow: PropTypes.oneOf(['row', 'column', 'dense', 'row dense', 'column dense']),
/**
* Items to be aligned in the grid.
*/
children: PropTypes.node,
+ /**
+ * Gap between columns. Accepts any valid value of `grid-column-gap` CSS property.
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap) for more.
+ */
+ columnGap: PropTypes.oneOf([
+ PropTypes.string,
+ PropTypes.shape({
+ xs: PropTypes.string,
+ sm: PropTypes.string,
+ md: PropTypes.string,
+ lg: PropTypes.string,
+ xl: PropTypes.string,
+ xxl: PropTypes.string,
+ xxxl: PropTypes.string,
+ }),
+ ]),
+ /**
+ * Grid columns. Accepts any valid value of `grid-template-columns` CSS property.
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns) for more.
+ */
+ columns: PropTypes.oneOf([
+ PropTypes.string,
+ PropTypes.shape({
+ xs: PropTypes.string,
+ sm: PropTypes.string,
+ md: PropTypes.string,
+ lg: PropTypes.string,
+ xl: PropTypes.string,
+ xxl: PropTypes.string,
+ xxxl: PropTypes.string,
+ }),
+ ]),
/**
* ID of the root HTML element.
*/
id: PropTypes.string,
+ /**
+ * Gap between rows. Accepts any valid value of `grid-row-gap` CSS property.
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap) for more.
+ */
+ rowGap: PropTypes.oneOf([
+ PropTypes.string,
+ PropTypes.shape({
+ xs: PropTypes.string,
+ sm: PropTypes.string,
+ md: PropTypes.string,
+ lg: PropTypes.string,
+ xl: PropTypes.string,
+ xxl: PropTypes.string,
+ xxxl: PropTypes.string,
+ }),
+ ]),
+ /**
+ * Grid rows. Accepts any valid value of `grid-template-rows` CSS property.
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows) for more.
+ */
+ rows: PropTypes.oneOf([
+ PropTypes.string,
+ PropTypes.shape({
+ xs: PropTypes.string,
+ sm: PropTypes.string,
+ md: PropTypes.string,
+ lg: PropTypes.string,
+ xl: PropTypes.string,
+ xxl: PropTypes.string,
+ xxxl: PropTypes.string,
+ }),
+ ]),
};
export default Grid;
diff --git a/src/lib/components/layout/Grid/Grid.scss b/src/lib/components/layout/Grid/Grid.scss
index 65e22001e..1b0caa025 100644
--- a/src/lib/components/layout/Grid/Grid.scss
+++ b/src/lib/components/layout/Grid/Grid.scss
@@ -1,17 +1,56 @@
+// 1. Read value of `--rui-local--` that might have been defined by
+// JavaScript and assign it to `--rui-local-` used in 2.
+//
+// Fallback cascade containing fallbacks for all previous breakpoints recursively is included
+// using CSS custom property fallback mechanism like this:
+//
+// Fallback for `xs` breakpoint: ``
+// Fallback for `sm` breakpoint: `var(--rui-local--xs, )`
+// Fallback for `md` breakpoint: `var(--rui-local--sm, var(--rui-local--xs, ))`
+//
+// β¦ etc, up to the largest breakpoint.
+//
+// A media query is then created for each breakpoint (with exception of `xs` which doesn't need a
+// media query) and a corresponding responsive custom property variant is assigned to
+// `--rui-local-` that is used later in CSS, see 2.
+//
+// Example for `sm` breakpoint:
+//
+// `--rui-local-: var(--rui-local--sm, var(--rui-local--xs, ))`
+//
+// 2. Apply custom property value that is defined within current breakpoint, see 1.
+//
+// 3. Any valid auto-flow algorithm can be used. It's applied globally for all breakpoints.
+
@import '../../../styles/settings/layouts';
-@import '../../../styles/tools/breakpoints';
-@import './theme';
+@import 'mixins';
-/* autoprefixer grid: off */
.root {
+ $_properties: (
+ columns: var(--rui-grid-columns),
+ column-gap: var(--rui-grid-column-gap),
+ rows: var(--rui-grid-rows),
+ row-gap: var(--rui-grid-row-gap),
+ );
+
+ @include assign-responsive-custom-properties($_properties); // 1.
+
display: grid;
- grid-template-columns: 1fr;
- grid-template-rows: auto;
- grid-gap: $grid-gap;
+ grid-template-columns: var(--rui-local-columns); // 2.
+ grid-template-rows: var(--rui-local-rows); // 2.
+ grid-gap: var(--rui-local-row-gap) var(--rui-local-column-gap); // 2.
+ grid-auto-flow: var(--rui-local-auto-flow, var(--rui-grid-auto-flow)); // 3.
margin-bottom: $layout-common-bottom-spacing;
+}
+
+.span {
+ $_properties: (
+ column-span: 1,
+ row-span: 1,
+ );
+
+ @include assign-responsive-custom-properties($_properties); // 1.
- @include breakpoint-up(sm) {
- grid-template-columns: repeat(auto-fit, minmax($grid-item-min-width, $grid-item-max-width));
- justify-content: start;
- }
+ grid-column: span var(--rui-local-column-span, 1); // 2.
+ grid-row: span var(--rui-local-row-span, 1); // 2.
}
diff --git a/src/lib/components/layout/Grid/GridSpan.jsx b/src/lib/components/layout/Grid/GridSpan.jsx
new file mode 100644
index 000000000..022368a7c
--- /dev/null
+++ b/src/lib/components/layout/Grid/GridSpan.jsx
@@ -0,0 +1,83 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { generateResponsiveCustomProperties } from './helpers/generateResponsiveCustomProperties';
+import styles from './Grid.scss';
+
+export const GridSpan = ({
+ children,
+ columns,
+ id,
+ rows,
+ ...other
+}) => {
+ if (!children) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+/* Breakpoints are easier to work with when ordered according to their value, not name. */
+/* eslint-disable sort-keys */
+
+GridSpan.defaultProps = {
+ children: null,
+ columns: undefined,
+ id: undefined,
+ rows: undefined,
+};
+
+GridSpan.propTypes = {
+ /**
+ * Items to be aligned in the grid.
+ */
+ children: PropTypes.node,
+ /**
+ * Number of columns to span.
+ */
+ columns: PropTypes.oneOf([
+ PropTypes.number,
+ PropTypes.shape({
+ xs: PropTypes.number,
+ sm: PropTypes.number,
+ md: PropTypes.number,
+ lg: PropTypes.number,
+ xl: PropTypes.number,
+ xxl: PropTypes.number,
+ xxxl: PropTypes.number,
+ }),
+ ]),
+ /**
+ * ID of the root HTML element.
+ */
+ id: PropTypes.string,
+ /**
+ * Number of rows to span.
+ */
+ rows: PropTypes.oneOf([
+ PropTypes.number,
+ PropTypes.shape({
+ xs: PropTypes.number,
+ sm: PropTypes.number,
+ md: PropTypes.number,
+ lg: PropTypes.number,
+ xl: PropTypes.number,
+ xxl: PropTypes.number,
+ xxxl: PropTypes.number,
+ }),
+ ]),
+};
+
+export default GridSpan;
diff --git a/src/lib/components/layout/Grid/README.mdx b/src/lib/components/layout/Grid/README.mdx
index fca6dd7cc..989cf70d8 100644
--- a/src/lib/components/layout/Grid/README.mdx
+++ b/src/lib/components/layout/Grid/README.mdx
@@ -12,6 +12,7 @@ import {
} from 'docz'
import { Placeholder } from '../../../../docs/_components/Placeholder/Placeholder'
import { Grid } from './Grid'
+import { GridSpan } from './GridSpan'
The responsive Grid layout aligns content into an organized grid.
@@ -26,7 +27,9 @@ import { Grid } from '@react-ui-org/react-ui';
And use it:
-
+
+ Grid item
+ Grid item
Grid item
Grid item
Grid item
@@ -38,23 +41,183 @@ See [API](#api) for all available options.
## General Guidelines
-- To align your items in the grid, **simply wrap** them with the Grid
- component. Unlike other grid frameworks, **no additional markup** like
- GridItem or Cell is necessary. This is possible thanks to the
+- This component implements native
[CSS grid layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout),
- the right tool for two-dimensional layouts.
+ the right CSS tool for two-dimensional layouts. You may use any value accepted
+ by
+ [grid-template-columns](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns),
+ [grid-template-rows](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows),
+ [grid-column-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap),
+ [grid-row-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap), and
+ [grid-auto-flow](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
+ CSS properties in corresponding API options of the component.
+
+- To align your items in the grid, **simply wrap** them with the Grid
+ component. Unlike other grid frameworks and UI libraries, **no additional
+ markup** like GridItem or Cell is necessary for your items. But it's there
+ when you really need itβsee [Advanced Layouts](#advanced-layouts).
+
+- The Grid component is so powerful that it enables you to build even very
+ advanced layouts **without** having to write **a single line of custom CSS.**
+ See [Advanced Layouts](#advanced-layouts) for more.
+
+π The default layout has one column, auto-sized rows and gaps of
+[size 4](/foundation/spacing). Defaults for all Grid API options can be
+[customized](/customize/theming) with CSS custom properties.
+
+## Columns and Rows
+
+Use `columns` and `rows` to specify your grid layout.
+
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+
+You can use the
+[`repeat()`](https://developer.mozilla.org/en-US/docs/Web/CSS/repeat) function
+to specify a recurring pattern.
+
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+
+Combine `repeat()` with `auto-fit` and
+[`minmax()`](https://developer.mozilla.org/en-US/docs/Web/CSS/minmax) to build
+automatic responsive layouts. Resize the playground to see it in action.
+
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+
+π If you need your items to have **equal height** even with content of varying
+length, it may be necessary to set `height: 100%` on them.
+
+## Gaps
-- If you need your items to have **equal height** even with content of varying
- length, it may be necessary to set `height: 100%` on them.
+Both column and row gaps can be customized.
-π The default column width algorithm is `repeat(auto-fit, minmax(300px, auto))`
-and it can be [customized](/customize/theming) with CSS custom properties.
-More layout options are [coming soon](https://github.com/react-ui-org/react-ui/issues/149)!
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+
+## Media Queries
+
+If you need to build more complicated layouts, you have full control over the
+grid definition. Just specify your grid layout for
+[breakpoints](/foundation/breakpoints) where a change of layout is needed.
+The Grid component is written with the mobile-first approach so values for small
+breakpoints are used until they're overriden by a bigger breakpoint. If `xs`
+settings are omitted, theme defaults are used. Resize your browser to see how it
+works.
+
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+
+## Advanced Layouts
+
+Wrap your content with GridSpan component to span it over multiple columns or
+rows. Use the `autoFlow` option to control the layout when combined with
+responsive columns and rows.
+
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+ Grid item spanning over two lines and two rows
+
+
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+ Grid item
+
+
+
+π `autoFlow` implements the `grid-auto-flow` CSS property. See
+[MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow) to fully
+understand available options.
## API
+### GridSpan
+
+Wrapper for content that should span multiple rows or columns.
+
+
+
---
Next: [List β](/components/layout/list)
diff --git a/src/lib/components/layout/Grid/__tests__/Grid.test.jsx b/src/lib/components/layout/Grid/__tests__/Grid.test.jsx
index a6a4ccd4f..c246e3b14 100644
--- a/src/lib/components/layout/Grid/__tests__/Grid.test.jsx
+++ b/src/lib/components/layout/Grid/__tests__/Grid.test.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import { shallow } from 'enzyme';
-import { Card } from '../../../ui/Card/Card';
import { Grid } from '../Grid';
describe('rendering', () => {
@@ -15,7 +14,7 @@ describe('rendering', () => {
it('renders correctly with a single child', () => {
const tree = shallow((
- content
+ content
));
@@ -25,9 +24,9 @@ describe('rendering', () => {
it('renders correctly with multiple children', () => {
const tree = shallow((
- content 1
- content 2
- content 3
+ content 1
+ content 2
+ content 3
));
diff --git a/src/lib/components/layout/Grid/__tests__/GridSpan.test.jsx b/src/lib/components/layout/Grid/__tests__/GridSpan.test.jsx
new file mode 100644
index 000000000..1d9ec1ab6
--- /dev/null
+++ b/src/lib/components/layout/Grid/__tests__/GridSpan.test.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { GridSpan } from '../GridSpan';
+
+describe('rendering', () => {
+ it('renders correctly with no children', () => {
+ const tree = shallow((
+
+ ));
+
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('renders correctly with a single child', () => {
+ const tree = shallow((
+
+ content
+
+ ));
+
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('renders correctly with multiple children', () => {
+ const tree = shallow((
+
+ content 1
+ content 2
+ content 3
+
+ ));
+
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/lib/components/layout/Grid/__tests__/__snapshots__/Grid.test.jsx.snap b/src/lib/components/layout/Grid/__tests__/__snapshots__/Grid.test.jsx.snap
index 5eb9e14e9..31fe5bbb9 100644
--- a/src/lib/components/layout/Grid/__tests__/__snapshots__/Grid.test.jsx.snap
+++ b/src/lib/components/layout/Grid/__tests__/__snapshots__/Grid.test.jsx.snap
@@ -3,54 +3,36 @@
exports[`rendering renders correctly with a single child 1`] = `
`;
exports[`rendering renders correctly with multiple children 1`] = `
-
+
content 1
-
-
+
+
content 2
-
-
+
+
content 3
-
+
`;
diff --git a/src/lib/components/layout/Grid/__tests__/__snapshots__/GridSpan.test.jsx.snap b/src/lib/components/layout/Grid/__tests__/__snapshots__/GridSpan.test.jsx.snap
new file mode 100644
index 000000000..f7ea3626a
--- /dev/null
+++ b/src/lib/components/layout/Grid/__tests__/__snapshots__/GridSpan.test.jsx.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`rendering renders correctly with a single child 1`] = `
+
+`;
+
+exports[`rendering renders correctly with multiple children 1`] = `
+
+
+ content 1
+
+
+ content 2
+
+
+ content 3
+
+
+`;
+
+exports[`rendering renders correctly with no children 1`] = `""`;
diff --git a/src/lib/components/layout/Grid/_mixins.scss b/src/lib/components/layout/Grid/_mixins.scss
new file mode 100644
index 000000000..0a4cfb372
--- /dev/null
+++ b/src/lib/components/layout/Grid/_mixins.scss
@@ -0,0 +1,37 @@
+@import '../../../styles/settings/breakpoints';
+@import '../../../styles/tools/breakpoints';
+
+// Generate fallback cascade using `var()` function fallbacks.
+//
+// $property-name: Custom property name
+// $initial-fallback: Initial fallback value
+// $current-breakpoint: Generate cascade for breakpoints smaller than this one
+@function _generate-custom-property-fallback-cascade($property-name, $initial-fallback, $current-breakpoint) {
+ $fallback-cascade: $initial-fallback;
+
+ @each $breakpoint in map-keys($breakpoint-values) {
+ @if $breakpoint == $current-breakpoint {
+ @return $fallback-cascade;
+ }
+
+ $fallback-cascade: var(--rui-local-#{$property-name}-#{$breakpoint}, $fallback-cascade);
+ }
+}
+
+// Read custom properties within a given breakpoint and assign them to expected output custom
+// properties. Use a generated fallback cascade should the custom property be undefined.
+//
+// $properties: