Skip to content

Commit 55ba106

Browse files
lee-chasetay1orjonesheloiselui
authored
feat: add withRowGap property to grid (#21430)
* feat: add withRowGap property to grid * fix: address review issues * fix: only direct descendant flex row gap * fix: subgrid row gap * fix: sub rowgap for web components * fix: use cds-grid-gutter-start as per column gap * chore: yarn format --------- Co-authored-by: Taylor Jones <tay1orjones@users.noreply.github.com> Co-authored-by: Heloise Lui <71858203+heloiselui@users.noreply.github.com> Co-authored-by: “heloiselui” <helolui27@gmail.com>
1 parent 046c448 commit 55ba106

17 files changed

Lines changed: 733 additions & 16 deletions

File tree

packages/grid/scss/_css-grid.scss

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,21 @@
150150
)};
151151
}
152152

153+
// -----------------------------------------------------------------------------
154+
// Row gap
155+
// -----------------------------------------------------------------------------
156+
157+
// Add row gap support for CSS Grid
158+
// Row gap should match the total gutter (start + end margins)
159+
.#{$prefix}--css-grid--with-row-gap {
160+
row-gap: var(--cds-grid-gutter);
161+
}
162+
163+
// Narrow mode has 0 + 1rem = 1rem total gutter
164+
.#{$prefix}--css-grid--narrow.#{$prefix}--css-grid--with-row-gap {
165+
row-gap: calc(var(--cds-grid-gutter) / 2);
166+
}
167+
153168
// -----------------------------------------------------------------------------
154169
// Alignment
155170
// -----------------------------------------------------------------------------
@@ -193,6 +208,7 @@
193208
}
194209

195210
.#{$prefix}--subgrid--condensed {
211+
--cds-grid-gutter: #{$grid-gutter-condensed};
196212
--cds-grid-gutter-start: #{math.div($grid-gutter-condensed, 2)};
197213
--cds-grid-gutter-end: #{math.div($grid-gutter-condensed, 2)};
198214
--cds-grid-column-hang: #{math.div($grid-gutter, 2) - math.div(
@@ -201,6 +217,25 @@
201217
)};
202218
}
203219

220+
// -----------------------------------------------------------------------------
221+
// Row gap
222+
// -----------------------------------------------------------------------------
223+
224+
// Add row gap support for CSS subgrids
225+
// Row gap should match the total gutter (start + end margins)
226+
.#{$prefix}--subgrid--with-row-gap {
227+
row-gap: #{$grid-gutter};
228+
}
229+
230+
// Narrow mode has 0 + 1rem = 1rem total gutter
231+
.#{$prefix}--subgrid--narrow.#{$prefix}--subgrid--with-row-gap {
232+
row-gap: #{math.div($grid-gutter, 2)};
233+
}
234+
235+
.#{$prefix}--subgrid--condensed.#{$prefix}--subgrid--with-row-gap {
236+
row-gap: #{$grid-gutter-condensed};
237+
}
238+
204239
// -----------------------------------------------------------------------------
205240
// Column hang
206241
// -----------------------------------------------------------------------------

packages/grid/scss/_flex-grid.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,32 @@
318318
padding-block: $condensed-gutter * 0.5;
319319
}
320320

321+
// Add row gap support for Flexbox Grid using gap and margin-block-end
322+
.#{$prefix}--grid--with-row-gap {
323+
--cds-grid-row-gap: #{$grid-gutter};
324+
}
325+
326+
// Sets the gap between rows and wrapped columns
327+
.#{$prefix}--grid--with-row-gap > .#{$prefix}--row {
328+
gap: var(--cds-grid-row-gap) 0;
329+
margin-block-end: var(--cds-grid-row-gap);
330+
}
331+
332+
// Condensed mode has 0 in FlexGrid
333+
.#{$prefix}--grid--with-row-gap.#{$prefix}--grid--condensed {
334+
--cds-grid-row-gap: 0;
335+
}
336+
337+
// Narrow mode has 0 + 1rem = 1rem total gutter
338+
.#{$prefix}--grid--with-row-gap.#{$prefix}--grid--narrow {
339+
--cds-grid-row-gap: calc(#{$grid-gutter} / 2);
340+
}
341+
342+
// Remove margin from the last row
343+
.#{$prefix}--grid--with-row-gap > .#{$prefix}--row:last-child {
344+
margin-block-end: 0;
345+
}
346+
321347
@include -make-grid-columns($breakpoints, $grid-gutter);
322348
@include -no-gutter();
323349
@include -hang($grid-gutter);

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4155,6 +4155,9 @@ Map {
41554155
"narrow": {
41564156
"type": "bool",
41574157
},
4158+
"withRowGap": {
4159+
"type": "bool",
4160+
},
41584161
},
41594162
"render": [Function],
41604163
},
@@ -5490,6 +5493,9 @@ Map {
54905493
"narrow": {
54915494
"type": "bool",
54925495
},
5496+
"withRowGap": {
5497+
"type": "bool",
5498+
},
54935499
},
54945500
},
54955501
"GridSettings" => {

packages/react/src/components/Grid/CSSGrid.tsx

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const CSSGrid = React.forwardRef<
2929
condensed = false,
3030
fullWidth = false,
3131
narrow = false,
32+
withRowGap,
3233
...rest
3334
},
3435
ref?
@@ -50,6 +51,7 @@ const CSSGrid = React.forwardRef<
5051
as={as}
5152
className={customClassName}
5253
mode={mode}
54+
withRowGap={withRowGap}
5355
{...rest}>
5456
{children}
5557
</Subgrid>
@@ -64,6 +66,7 @@ const CSSGrid = React.forwardRef<
6466
[`${prefix}--css-grid--full-width`]: fullWidth,
6567
[`${prefix}--css-grid--start`]: align === 'start',
6668
[`${prefix}--css-grid--end`]: align === 'end',
69+
[`${prefix}--css-grid--with-row-gap`]: withRowGap,
6770
});
6871

6972
// cast as any to let TypeScript allow passing in attributes to base component
@@ -115,6 +118,12 @@ CSSGrid.propTypes = {
115118
* typographic alignment with and without containers.
116119
*/
117120
narrow: PropTypes.bool,
121+
122+
/**
123+
* Add a row gap to the grid that matches the current gutter size.
124+
* This is useful when you want consistent vertical spacing between rows.
125+
*/
126+
withRowGap: PropTypes.bool,
118127
};
119128

120129
type SubgridMode = 'wide' | 'narrow' | 'condensed';
@@ -134,6 +143,12 @@ interface SubgridBaseProps {
134143
* Specify the gutter mode for the subgrid
135144
*/
136145
mode?: SubgridMode;
146+
147+
/**
148+
* Add a row gap to the subgrid that matches the current gutter size.
149+
* This is useful when you want consistent vertical spacing between rows.
150+
*/
151+
withRowGap?: boolean;
137152
}
138153

139154
// eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452
@@ -143,21 +158,27 @@ const Subgrid = React.forwardRef<
143158
SubgridBaseProps & {
144159
as?: React.ElementType;
145160
} & React.HTMLAttributes<HTMLDivElement>
146-
>(({ as, className: customClassName, children, mode, ...rest }, ref) => {
147-
const prefix = usePrefix();
148-
const className = cx(customClassName, {
149-
[`${prefix}--subgrid`]: true,
150-
[`${prefix}--subgrid--condensed`]: mode === 'condensed',
151-
[`${prefix}--subgrid--narrow`]: mode === 'narrow',
152-
[`${prefix}--subgrid--wide`]: mode === 'wide',
153-
});
154-
const BaseComponent = as || 'div';
155-
return (
156-
<BaseComponent {...rest} ref={ref} className={className}>
157-
{children}
158-
</BaseComponent>
159-
);
160-
});
161+
>(
162+
(
163+
{ as, className: customClassName, children, mode, withRowGap, ...rest },
164+
ref
165+
) => {
166+
const prefix = usePrefix();
167+
const className = cx(customClassName, {
168+
[`${prefix}--subgrid`]: true,
169+
[`${prefix}--subgrid--condensed`]: mode === 'condensed',
170+
[`${prefix}--subgrid--narrow`]: mode === 'narrow',
171+
[`${prefix}--subgrid--wide`]: mode === 'wide',
172+
[`${prefix}--subgrid--with-row-gap`]: withRowGap,
173+
});
174+
const BaseComponent = as || 'div';
175+
return (
176+
<BaseComponent {...rest} ref={ref} className={className}>
177+
{children}
178+
</BaseComponent>
179+
);
180+
}
181+
);
161182

162183
Subgrid.propTypes = {
163184
/**
@@ -179,6 +200,12 @@ Subgrid.propTypes = {
179200
* Specify the gutter mode for the subgrid
180201
*/
181202
mode: PropTypes.oneOf(['wide', 'narrow', 'condensed'] as SubgridMode[]),
203+
204+
/**
205+
* Add a row gap to the grid that matches the current gutter size.
206+
* This is useful when you want consistent vertical spacing between rows.
207+
*/
208+
withRowGap: PropTypes.bool,
182209
};
183210

184211
const CSSGridComponent: GridComponent = CSSGrid;

packages/react/src/components/Grid/FlexGrid.mdx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { stackblitzPrefillConfig } from '../../../previewer/codePreviewer';
2424
- [Mix-and-match](#mix-and-match)
2525
- [Auto columns](#auto-columns)
2626
- [Offset columns](#offset-columns)
27+
- [Row gap](#row-gap)
2728
- [Component API](#component-api)
2829
- [Using the `as` prop](#using-the-as-prop)
2930
- [FAQ](#faq)
@@ -230,6 +231,78 @@ breakpoint. At the medium breakpoint, it will be offset by two columns.
230231
]}
231232
/>
232233

234+
## Row gap
235+
236+
The `FlexGrid` component supports an optional `withRowGap` prop that adds
237+
vertical spacing between rows. When enabled, the row gap automatically matches
238+
the current gutter mode:
239+
240+
- Default (wide): 32px row gap
241+
- Narrow: 16px row gap
242+
- Condensed: 1px row gap
243+
244+
This feature is useful when you want consistent vertical spacing between rows
245+
that matches your horizontal gutter spacing.
246+
247+
```jsx
248+
import { FlexGrid, Row, Column } from '@carbon/react';
249+
250+
function MyComponent() {
251+
return (
252+
<FlexGrid withRowGap>
253+
<Row>
254+
<Column lg={4}>Row 1, Column 1</Column>
255+
<Column lg={4}>Row 1, Column 2</Column>
256+
<Column lg={4}>Row 1, Column 3</Column>
257+
</Row>
258+
<Row>
259+
<Column lg={4}>Row 2, Column 1</Column>
260+
<Column lg={4}>Row 2, Column 2</Column>
261+
<Column lg={4}>Row 2, Column 3</Column>
262+
</Row>
263+
</FlexGrid>
264+
);
265+
}
266+
```
267+
268+
You can combine `withRowGap` with different gutter modes:
269+
270+
```jsx
271+
{
272+
/* Narrow grid with row gap */
273+
}
274+
<FlexGrid narrow withRowGap>
275+
<Row>
276+
<Column>Content</Column>
277+
</Row>
278+
<Row>
279+
<Column>Content</Column>
280+
</Row>
281+
</FlexGrid>;
282+
283+
{
284+
/* Condensed grid with row gap */
285+
}
286+
<FlexGrid condensed withRowGap>
287+
<Row>
288+
<Column>Content</Column>
289+
</Row>
290+
<Row>
291+
<Column>Content</Column>
292+
</Row>
293+
</FlexGrid>;
294+
```
295+
296+
<Canvas
297+
of={FlexGridStories.WithRowGap}
298+
additionalActions={[
299+
{
300+
title: 'Open in Stackblitz',
301+
onClick: () => stackblitzPrefillConfig(FlexGridStories.WithRowGap),
302+
},
303+
]}
304+
/>
305+
233306
## Component API
234307

235308
<ArgTypes />

packages/react/src/components/Grid/FlexGrid.stories.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,77 @@ Default.argTypes = {
453453
},
454454
},
455455
};
456+
457+
export const WithRowGap = (args) => {
458+
function DemoContent({ children }) {
459+
return (
460+
<div className="outside">
461+
<div className="inside">{children}</div>
462+
</div>
463+
);
464+
}
465+
return (
466+
<div id="templates">
467+
<FlexGrid {...args}>
468+
<Row>
469+
<Column sm={4} md={4} lg={4}>
470+
<DemoContent>Row 1, Col 1</DemoContent>
471+
</Column>
472+
<Column sm={4} md={4} lg={4}>
473+
<DemoContent>Row 1, Col 2</DemoContent>
474+
</Column>
475+
<Column sm={4} md={4} lg={4}>
476+
<DemoContent>Row 1, Col 3</DemoContent>
477+
</Column>
478+
<Column sm={4} md={4} lg={4}>
479+
<DemoContent>Row 1, Col 4</DemoContent>
480+
</Column>
481+
</Row>
482+
<Row>
483+
<Column sm={4} md={4} lg={4}>
484+
<DemoContent>Row 2, Col 1</DemoContent>
485+
</Column>
486+
<Column sm={4} md={4} lg={4}>
487+
<DemoContent>Row 2, Col 2</DemoContent>
488+
</Column>
489+
<Column sm={4} md={4} lg={4}>
490+
<DemoContent>Row 2, Col 3</DemoContent>
491+
</Column>
492+
<Column sm={4} md={4} lg={4}>
493+
<DemoContent>Row 2, Col 4</DemoContent>
494+
</Column>
495+
</Row>
496+
</FlexGrid>
497+
</div>
498+
);
499+
};
500+
501+
WithRowGap.args = {
502+
withRowGap: true,
503+
narrow: false,
504+
condensed: false,
505+
};
506+
507+
WithRowGap.argTypes = {
508+
withRowGap: {
509+
control: {
510+
type: 'boolean',
511+
},
512+
},
513+
narrow: {
514+
control: {
515+
type: 'boolean',
516+
},
517+
},
518+
condensed: {
519+
control: {
520+
type: 'boolean',
521+
},
522+
},
523+
children: {
524+
control: false,
525+
},
526+
className: {
527+
control: false,
528+
},
529+
};

0 commit comments

Comments
 (0)