@boostdev/design-system-components 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +114 -69
- package/dist/client.css +537 -537
- package/dist/client.d.cts +30 -26
- package/dist/client.d.ts +30 -26
- package/dist/client.js +114 -69
- package/dist/index.cjs +114 -69
- package/dist/index.css +537 -537
- package/dist/index.d.cts +30 -26
- package/dist/index.d.ts +30 -26
- package/dist/index.js +114 -69
- package/dist/web-components/index.d.ts +9 -5
- package/dist/web-components/index.js +55 -12
- package/package.json +1 -1
- package/src/components/layout/Grid/Grid.mdx +4 -2
- package/src/components/layout/Grid/Grid.spec.tsx +85 -6
- package/src/components/layout/Grid/Grid.stories.tsx +16 -9
- package/src/components/layout/Grid/Grid.tsx +11 -6
- package/src/components/layout/Grid/masonry.ts +135 -33
- package/src/web-components/layout/BdsGrid.mdx +5 -3
- package/src/web-components/layout/BdsGrid.stories.tsx +16 -10
- package/src/web-components/layout/bds-grid.spec.ts +3 -3
- package/src/web-components/layout/bds-grid.ts +19 -6
|
@@ -55,9 +55,12 @@ type GridVariant = 'main' | 'page' | 'funnel' | 'custom';
|
|
|
55
55
|
* auto-span-media — boolean; child `<bds-grid-item>`s without a `column-span`
|
|
56
56
|
* attribute resolve their span from the intrinsic aspect
|
|
57
57
|
* ratio of their first `<img>`/`<video>` child.
|
|
58
|
-
* masonry-preferred-column-span — number; masonry only. Child
|
|
59
|
-
* `column-span` attribute try to span this
|
|
60
|
-
*
|
|
58
|
+
* masonry-preferred-column-span — named span or number; masonry only. Child
|
|
59
|
+
* items without a `column-span` attribute try to span this
|
|
60
|
+
* value (e.g. `"half"`, `"one-third"`). Named values adapt
|
|
61
|
+
* across breakpoints via the foundation's responsive column
|
|
62
|
+
* tokens. An item at row-end is demoted to the remaining
|
|
63
|
+
* column count. Numeric values are literal and don't adapt.
|
|
61
64
|
*
|
|
62
65
|
* Slots:
|
|
63
66
|
* (default) — grid content; typically `<bds-grid-item>` children
|
|
@@ -89,7 +92,7 @@ declare class BdsGrid extends LitElement {
|
|
|
89
92
|
reflect: boolean;
|
|
90
93
|
};
|
|
91
94
|
masonryPreferredColumnSpan: {
|
|
92
|
-
type:
|
|
95
|
+
type: StringConstructor;
|
|
93
96
|
attribute: string;
|
|
94
97
|
reflect: boolean;
|
|
95
98
|
};
|
|
@@ -99,7 +102,7 @@ declare class BdsGrid extends LitElement {
|
|
|
99
102
|
isCentered: boolean;
|
|
100
103
|
isMasonry: boolean;
|
|
101
104
|
autoSpanMedia: boolean;
|
|
102
|
-
masonryPreferredColumnSpan:
|
|
105
|
+
masonryPreferredColumnSpan: string | undefined;
|
|
103
106
|
private _ro?;
|
|
104
107
|
private _mo?;
|
|
105
108
|
private _frame;
|
|
@@ -110,6 +113,7 @@ declare class BdsGrid extends LitElement {
|
|
|
110
113
|
private _items;
|
|
111
114
|
private _setupMasonry;
|
|
112
115
|
private _scheduleMasonry;
|
|
116
|
+
private _resolvePreferredColumnSpan;
|
|
113
117
|
private _teardownMasonry;
|
|
114
118
|
render(): lit.TemplateResult<1>;
|
|
115
119
|
}
|
|
@@ -128,35 +128,62 @@ import {
|
|
|
128
128
|
|
|
129
129
|
// src/components/layout/Grid/masonry.ts
|
|
130
130
|
import { useLayoutEffect } from "react";
|
|
131
|
+
var NAMED_SPAN_VAR = {
|
|
132
|
+
"three-quarters": "--bds-grid_columns-75",
|
|
133
|
+
"two-thirds": "--bds-grid_columns-66",
|
|
134
|
+
half: "--bds-grid_columns-50",
|
|
135
|
+
"one-third": "--bds-grid_columns-33",
|
|
136
|
+
"one-quarter": "--bds-grid_columns-25"
|
|
137
|
+
};
|
|
138
|
+
var NAMED_SPAN_FRACTION = {
|
|
139
|
+
"three-quarters": 0.75,
|
|
140
|
+
"two-thirds": 2 / 3,
|
|
141
|
+
half: 0.5,
|
|
142
|
+
"one-third": 1 / 3,
|
|
143
|
+
"one-quarter": 0.25
|
|
144
|
+
};
|
|
145
|
+
function resolvePreferredColumns(container, value, totalCols) {
|
|
146
|
+
if (value === "full") return totalCols;
|
|
147
|
+
if (typeof value === "number") return Math.max(1, Math.min(totalCols, value));
|
|
148
|
+
const varName = NAMED_SPAN_VAR[value];
|
|
149
|
+
const raw = window.getComputedStyle(container).getPropertyValue(varName).trim();
|
|
150
|
+
const n = parseInt(raw, 10);
|
|
151
|
+
if (Number.isFinite(n) && n > 0) return Math.min(totalCols, n);
|
|
152
|
+
return Math.max(1, Math.round(totalCols * NAMED_SPAN_FRACTION[value]));
|
|
153
|
+
}
|
|
131
154
|
var ROW_UNIT_PX = 1;
|
|
132
155
|
function supportsNativeMasonry() {
|
|
133
156
|
if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return false;
|
|
134
157
|
return CSS.supports("display", "grid-lanes") || CSS.supports("grid-template-rows", "masonry");
|
|
135
158
|
}
|
|
159
|
+
var ORIGINAL_COL_ATTR = "data-bds-masonry-original-column";
|
|
136
160
|
function applyMasonryLayout(container, rawChildren, options = {}) {
|
|
137
161
|
const cs = window.getComputedStyle(container);
|
|
138
162
|
const targetGap = parseFloat(cs.columnGap) || 0;
|
|
139
163
|
container.style.gridAutoRows = `${ROW_UNIT_PX}px`;
|
|
140
164
|
container.style.rowGap = "0px";
|
|
141
165
|
container.style.gridAutoFlow = "dense";
|
|
166
|
+
container.style.overflowAnchor = "none";
|
|
142
167
|
const children = rawChildren.filter(
|
|
143
168
|
(el) => el.nodeType === 1 && window.getComputedStyle(el).display !== "none"
|
|
144
169
|
);
|
|
145
170
|
for (const child of children) {
|
|
146
|
-
child.style.gridRow = "";
|
|
147
|
-
child.style.gridRowStart = "";
|
|
148
|
-
child.style.gridRowEnd = "";
|
|
149
171
|
child.style.alignSelf = "start";
|
|
172
|
+
child.style.overflowAnchor = "none";
|
|
150
173
|
}
|
|
151
|
-
const preferred = options.preferredColumnSpan
|
|
152
|
-
if (preferred
|
|
174
|
+
const preferred = options.preferredColumnSpan;
|
|
175
|
+
if (preferred !== void 0) {
|
|
153
176
|
const totalCols = countGridColumns(container);
|
|
154
|
-
|
|
177
|
+
const preferredCols = totalCols > 0 ? resolvePreferredColumns(container, preferred, totalCols) : 0;
|
|
178
|
+
if (totalCols > 0 && preferredCols > 0) {
|
|
155
179
|
let col = 0;
|
|
156
180
|
for (const child of children) {
|
|
157
181
|
if (child.hasAttribute("data-column-span-auto")) {
|
|
182
|
+
if (!child.hasAttribute(ORIGINAL_COL_ATTR)) {
|
|
183
|
+
child.setAttribute(ORIGINAL_COL_ATTR, child.style.gridColumn);
|
|
184
|
+
}
|
|
158
185
|
const remaining = totalCols - col;
|
|
159
|
-
const span = Math.max(1, Math.min(
|
|
186
|
+
const span = Math.max(1, Math.min(preferredCols, remaining));
|
|
160
187
|
child.style.gridColumn = `span ${span}`;
|
|
161
188
|
col = (col + span) % totalCols;
|
|
162
189
|
} else {
|
|
@@ -165,6 +192,10 @@ function applyMasonryLayout(container, rawChildren, options = {}) {
|
|
|
165
192
|
}
|
|
166
193
|
}
|
|
167
194
|
}
|
|
195
|
+
} else {
|
|
196
|
+
for (const child of children) {
|
|
197
|
+
restoreOriginalColumn(child);
|
|
198
|
+
}
|
|
168
199
|
}
|
|
169
200
|
const heights = children.map((child) => child.offsetHeight);
|
|
170
201
|
for (let i3 = 0; i3 < children.length; i3++) {
|
|
@@ -195,17 +226,22 @@ function clearMasonryLayout(container, rawChildren) {
|
|
|
195
226
|
container.style.gridAutoRows = "";
|
|
196
227
|
container.style.gridAutoFlow = "";
|
|
197
228
|
container.style.rowGap = "";
|
|
229
|
+
container.style.overflowAnchor = "";
|
|
198
230
|
for (const child of rawChildren) {
|
|
199
231
|
if (child.nodeType !== 1) continue;
|
|
200
232
|
child.style.gridRow = "";
|
|
201
233
|
child.style.gridRowStart = "";
|
|
202
234
|
child.style.gridRowEnd = "";
|
|
203
235
|
child.style.alignSelf = "";
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
236
|
+
child.style.overflowAnchor = "";
|
|
237
|
+
restoreOriginalColumn(child);
|
|
207
238
|
}
|
|
208
239
|
}
|
|
240
|
+
function restoreOriginalColumn(el) {
|
|
241
|
+
if (!el.hasAttribute(ORIGINAL_COL_ATTR)) return;
|
|
242
|
+
el.style.gridColumn = el.getAttribute(ORIGINAL_COL_ATTR) ?? "";
|
|
243
|
+
el.removeAttribute(ORIGINAL_COL_ATTR);
|
|
244
|
+
}
|
|
209
245
|
|
|
210
246
|
// src/web-components/layout/bds-grid.ts
|
|
211
247
|
var BdsGrid = class extends i2 {
|
|
@@ -264,7 +300,7 @@ var BdsGrid = class extends i2 {
|
|
|
264
300
|
isMasonry: { type: Boolean, attribute: "is-masonry", reflect: true },
|
|
265
301
|
autoSpanMedia: { type: Boolean, attribute: "auto-span-media", reflect: true },
|
|
266
302
|
masonryPreferredColumnSpan: {
|
|
267
|
-
type:
|
|
303
|
+
type: String,
|
|
268
304
|
attribute: "masonry-preferred-column-span",
|
|
269
305
|
reflect: true
|
|
270
306
|
}
|
|
@@ -321,11 +357,18 @@ var BdsGrid = class extends i2 {
|
|
|
321
357
|
const container = this._gridEl();
|
|
322
358
|
if (container) {
|
|
323
359
|
applyMasonryLayout(container, this._items(), {
|
|
324
|
-
preferredColumnSpan: this.
|
|
360
|
+
preferredColumnSpan: this._resolvePreferredColumnSpan()
|
|
325
361
|
});
|
|
326
362
|
}
|
|
327
363
|
});
|
|
328
364
|
}
|
|
365
|
+
_resolvePreferredColumnSpan() {
|
|
366
|
+
const raw = this.masonryPreferredColumnSpan;
|
|
367
|
+
if (raw === void 0 || raw === null || raw === "") return void 0;
|
|
368
|
+
const n = Number(raw);
|
|
369
|
+
if (Number.isFinite(n) && String(n) === raw) return n;
|
|
370
|
+
return raw;
|
|
371
|
+
}
|
|
329
372
|
_teardownMasonry() {
|
|
330
373
|
cancelAnimationFrame(this._frame);
|
|
331
374
|
this._ro?.disconnect();
|
package/package.json
CHANGED
|
@@ -203,16 +203,18 @@ Intrinsic dimensions are read from `img.naturalWidth/naturalHeight` and `video.v
|
|
|
203
203
|
|
|
204
204
|
## Masonry preferred column span
|
|
205
205
|
|
|
206
|
-
For a uniform "cards mostly
|
|
206
|
+
For a uniform "cards mostly half-width" feed, set `masonryPreferredColumnSpan` on the `Grid`. Items without an explicit `columnSpan` try to span that named fraction of the row. When an item would sit at the end of a row with fewer columns remaining than the preferred span, the polyfill **demotes** its span to just fill the remaining columns — the item never wraps to a new row leaving a visible gap behind it.
|
|
207
207
|
|
|
208
208
|
```tsx
|
|
209
|
-
<Grid isMasonry masonryPreferredColumnSpan=
|
|
209
|
+
<Grid isMasonry masonryPreferredColumnSpan="half">
|
|
210
210
|
{cards.map((c) => (
|
|
211
211
|
<GridItem key={c.id}>…</GridItem>
|
|
212
212
|
))}
|
|
213
213
|
</Grid>
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
+
The accepted values match `columnSpan` on `GridItem`: `'full'`, `'three-quarters'`, `'two-thirds'`, `'half'`, `'one-third'`, `'one-quarter'`. Named values resolve through the foundation's responsive column tokens, so the same option adapts across breakpoints — `"half"` yields 2 cols on a 4-col mobile grid, 4 cols on an 8-col tablet grid, and 6 cols on a 12-col desktop grid. A `number` is accepted too, but is a literal column count and does **not** adapt — prefer named values unless you're using `variant="custom"`.
|
|
217
|
+
|
|
216
218
|
Rules:
|
|
217
219
|
|
|
218
220
|
- Requires `isMasonry`. No-op on non-masonry grids.
|
|
@@ -101,6 +101,46 @@ describe('applyMasonryLayout', () => {
|
|
|
101
101
|
expect(container.style.rowGap).toBe('0px');
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
it('sets overflow-anchor: none on the container to prevent scroll-anchor jitter', () => {
|
|
105
|
+
// Regression: `grid-auto-flow: dense` + per-child ResizeObserver caused
|
|
106
|
+
// scroll anchoring to yank the viewport up when an item above the fold resized
|
|
107
|
+
// (scrollbar toggle, late image decode). Opting the container out makes the
|
|
108
|
+
// browser anchor against content outside the grid instead.
|
|
109
|
+
const container = makeContainer(10);
|
|
110
|
+
applyMasonryLayout(container, []);
|
|
111
|
+
expect(container.style.overflowAnchor).toBe('none');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('clearMasonryLayout restores overflow-anchor', () => {
|
|
115
|
+
const container = makeContainer(10);
|
|
116
|
+
applyMasonryLayout(container, []);
|
|
117
|
+
clearMasonryLayout(container, []);
|
|
118
|
+
expect(container.style.overflowAnchor).toBe('');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('sets overflow-anchor: none on each child so the browser picks an anchor outside the grid', () => {
|
|
122
|
+
// `overflow-anchor` doesn't inherit — setting it only on the container leaves
|
|
123
|
+
// children anchor-eligible, so a child above the viewport being rewritten still
|
|
124
|
+
// drags the scroll position. Excluding each child forces the browser to anchor
|
|
125
|
+
// on content outside the grid.
|
|
126
|
+
const container = makeContainer(10);
|
|
127
|
+
const a = makeItem(100);
|
|
128
|
+
const b = makeItem(200);
|
|
129
|
+
container.append(a, b);
|
|
130
|
+
applyMasonryLayout(container, [a, b]);
|
|
131
|
+
expect(a.style.overflowAnchor).toBe('none');
|
|
132
|
+
expect(b.style.overflowAnchor).toBe('none');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('clearMasonryLayout restores overflow-anchor on children', () => {
|
|
136
|
+
const container = makeContainer(10);
|
|
137
|
+
const a = makeItem(100);
|
|
138
|
+
container.append(a);
|
|
139
|
+
applyMasonryLayout(container, [a]);
|
|
140
|
+
clearMasonryLayout(container, [a]);
|
|
141
|
+
expect(a.style.overflowAnchor).toBe('');
|
|
142
|
+
});
|
|
143
|
+
|
|
104
144
|
it('assigns grid-row-end: span ceil(height + targetGap) to each item', () => {
|
|
105
145
|
const container = makeContainer(10);
|
|
106
146
|
const a = makeItem(100);
|
|
@@ -179,12 +219,13 @@ describe('applyMasonryLayout', () => {
|
|
|
179
219
|
}
|
|
180
220
|
|
|
181
221
|
it('assigns the preferred span to data-column-span-auto items that fit', () => {
|
|
222
|
+
// 'half' in a 4-col grid → 2 cols (via fraction fallback; jsdom has no foundation CSS).
|
|
182
223
|
const container = makeContainer(10);
|
|
183
224
|
container.style.gridTemplateColumns = '100px 100px 100px 100px';
|
|
184
225
|
const a = makeAutoItem(50);
|
|
185
226
|
const b = makeAutoItem(50);
|
|
186
227
|
container.append(a, b);
|
|
187
|
-
applyMasonryLayout(container, [a, b], { preferredColumnSpan:
|
|
228
|
+
applyMasonryLayout(container, [a, b], { preferredColumnSpan: 'half' });
|
|
188
229
|
expect(a.style.gridColumn).toBe('span 2');
|
|
189
230
|
expect(b.style.gridColumn).toBe('span 2');
|
|
190
231
|
});
|
|
@@ -195,7 +236,8 @@ describe('applyMasonryLayout', () => {
|
|
|
195
236
|
const a = makeAutoItem(50);
|
|
196
237
|
const b = makeAutoItem(50);
|
|
197
238
|
container.append(a, b);
|
|
198
|
-
|
|
239
|
+
// 'two-thirds' in a 3-col grid → 2 cols.
|
|
240
|
+
applyMasonryLayout(container, [a, b], { preferredColumnSpan: 'two-thirds' });
|
|
199
241
|
expect(a.style.gridColumn).toBe('span 2');
|
|
200
242
|
// Only 1 col remains after `a` — `b` gets demoted.
|
|
201
243
|
expect(b.style.gridColumn).toBe('span 1');
|
|
@@ -208,7 +250,7 @@ describe('applyMasonryLayout', () => {
|
|
|
208
250
|
explicit.style.gridColumn = 'span 3';
|
|
209
251
|
const auto = makeAutoItem(50);
|
|
210
252
|
container.append(explicit, auto);
|
|
211
|
-
applyMasonryLayout(container, [explicit, auto], { preferredColumnSpan:
|
|
253
|
+
applyMasonryLayout(container, [explicit, auto], { preferredColumnSpan: 'half' });
|
|
212
254
|
expect(explicit.style.gridColumn).toBe('span 3');
|
|
213
255
|
// 3 cols consumed, 1 remains → auto demoted.
|
|
214
256
|
expect(auto.style.gridColumn).toBe('span 1');
|
|
@@ -223,16 +265,53 @@ describe('applyMasonryLayout', () => {
|
|
|
223
265
|
expect(a.style.gridColumn).toBe('');
|
|
224
266
|
});
|
|
225
267
|
|
|
226
|
-
it('
|
|
268
|
+
it('resolves named spans through foundation CSS vars when present', () => {
|
|
269
|
+
const container = makeContainer(10);
|
|
270
|
+
container.style.gridTemplateColumns = '100px 100px 100px 100px 100px 100px 100px 100px';
|
|
271
|
+
// Override the foundation var to a non-fractional value to prove we read it.
|
|
272
|
+
container.style.setProperty('--bds-grid_columns-50', '5');
|
|
273
|
+
const a = makeAutoItem(50);
|
|
274
|
+
container.append(a);
|
|
275
|
+
applyMasonryLayout(container, [a], { preferredColumnSpan: 'half' });
|
|
276
|
+
expect(a.style.gridColumn).toBe('span 5');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('accepts numeric preferredColumnSpan as a literal column count', () => {
|
|
280
|
+
const container = makeContainer(10);
|
|
281
|
+
container.style.gridTemplateColumns = '100px 100px 100px 100px';
|
|
282
|
+
const a = makeAutoItem(50);
|
|
283
|
+
const b = makeAutoItem(50);
|
|
284
|
+
container.append(a, b);
|
|
285
|
+
applyMasonryLayout(container, [a, b], { preferredColumnSpan: 1 });
|
|
286
|
+
expect(a.style.gridColumn).toBe('span 1');
|
|
287
|
+
expect(b.style.gridColumn).toBe('span 1');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('restores the original gridColumn when preferredColumnSpan is removed', () => {
|
|
291
|
+
// Simulates a Storybook control going preferred='half' → undefined.
|
|
292
|
+
// Without restore, items would stay pinned at `span 2`.
|
|
293
|
+
const container = makeContainer(10);
|
|
294
|
+
container.style.gridTemplateColumns = '100px 100px 100px 100px';
|
|
295
|
+
const a = makeAutoItem(50);
|
|
296
|
+
a.style.gridColumn = 'var(--bds-grid_span-100)';
|
|
297
|
+
container.append(a);
|
|
298
|
+
applyMasonryLayout(container, [a], { preferredColumnSpan: 'half' });
|
|
299
|
+
expect(a.style.gridColumn).toBe('span 2');
|
|
300
|
+
applyMasonryLayout(container, [a]); // preferredColumnSpan dropped
|
|
301
|
+
expect(a.style.gridColumn).toBe('var(--bds-grid_span-100)');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('clearMasonryLayout restores gridColumn on data-column-span-auto items', () => {
|
|
227
305
|
const container = makeContainer(10);
|
|
228
306
|
container.style.gridTemplateColumns = '100px 100px 100px 100px';
|
|
229
307
|
const auto = makeAutoItem(50);
|
|
308
|
+
auto.style.gridColumn = 'var(--bds-grid_span-100)';
|
|
230
309
|
const explicit = makeItem(50);
|
|
231
310
|
explicit.style.gridColumn = 'span 2';
|
|
232
311
|
container.append(auto, explicit);
|
|
233
|
-
applyMasonryLayout(container, [auto, explicit], { preferredColumnSpan:
|
|
312
|
+
applyMasonryLayout(container, [auto, explicit], { preferredColumnSpan: 'half' });
|
|
234
313
|
clearMasonryLayout(container, [auto, explicit]);
|
|
235
|
-
expect(auto.style.gridColumn).toBe('');
|
|
314
|
+
expect(auto.style.gridColumn).toBe('var(--bds-grid_span-100)');
|
|
236
315
|
expect(explicit.style.gridColumn).toBe('span 2');
|
|
237
316
|
});
|
|
238
317
|
});
|
|
@@ -10,7 +10,10 @@ const meta = {
|
|
|
10
10
|
columns: { control: 'number' },
|
|
11
11
|
isCentered: { control: 'boolean' },
|
|
12
12
|
isMasonry: { control: 'boolean' },
|
|
13
|
-
masonryPreferredColumnSpan: {
|
|
13
|
+
masonryPreferredColumnSpan: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['full', 'three-quarters', 'two-thirds', 'half', 'one-third', 'one-quarter'],
|
|
16
|
+
},
|
|
14
17
|
},
|
|
15
18
|
} satisfies Meta<typeof Grid>;
|
|
16
19
|
|
|
@@ -142,17 +145,21 @@ export const MasonryWithSpans: Story = {
|
|
|
142
145
|
};
|
|
143
146
|
|
|
144
147
|
/**
|
|
145
|
-
* `masonryPreferredColumnSpan=
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
+
* `masonryPreferredColumnSpan="half"` makes every auto-span item try to span
|
|
149
|
+
* half the grid. Because `"half"` resolves through the foundation's responsive
|
|
150
|
+
* column tokens, the same value yields 2 cols on a 4-col mobile grid, 4 cols
|
|
151
|
+
* on an 8-col tablet grid, and 6 cols on a 12-col desktop grid — the layout
|
|
152
|
+
* adapts without you configuring per-breakpoint values.
|
|
153
|
+
*
|
|
154
|
+
* When an item would overflow the row, the polyfill **demotes** its span to
|
|
148
155
|
* fill just the remaining columns, so no trailing gap appears.
|
|
149
156
|
*
|
|
150
|
-
* Use the Controls to
|
|
151
|
-
* the packing re-run. Resize the viewport
|
|
152
|
-
*
|
|
157
|
+
* Use the Controls to swap `masonryPreferredColumnSpan` between named values
|
|
158
|
+
* and watch the packing re-run. Resize the viewport to see the demotion adapt
|
|
159
|
+
* to the current column count.
|
|
153
160
|
*/
|
|
154
161
|
export const MasonryPreferredColumnSpan: Story = {
|
|
155
|
-
args: { isMasonry: true, masonryPreferredColumnSpan:
|
|
162
|
+
args: { isMasonry: true, masonryPreferredColumnSpan: 'half' },
|
|
156
163
|
render: (args) => (
|
|
157
164
|
<Grid {...args}>
|
|
158
165
|
{Array.from({ length: 7 }).map((_, i) => (
|
|
@@ -170,7 +177,7 @@ export const MasonryPreferredColumnSpan: Story = {
|
|
|
170
177
|
* so the auto items fill the remaining row consistently.
|
|
171
178
|
*/
|
|
172
179
|
export const MasonryPreferredColumnSpanWithHero: Story = {
|
|
173
|
-
args: { isMasonry: true, masonryPreferredColumnSpan:
|
|
180
|
+
args: { isMasonry: true, masonryPreferredColumnSpan: 'half' },
|
|
174
181
|
render: (args) => (
|
|
175
182
|
<Grid {...args}>
|
|
176
183
|
<GridItem columnSpan="three-quarters" style={masonryCellStyle(0)}>
|
|
@@ -11,6 +11,7 @@ import css from './Grid.module.css';
|
|
|
11
11
|
import { cn } from '@boostdev/design-system-foundation';
|
|
12
12
|
import type { WithClassName } from '../../../types';
|
|
13
13
|
import { useMasonry } from './masonry';
|
|
14
|
+
import type { GridItemSpanValue } from './GridItem';
|
|
14
15
|
|
|
15
16
|
export type GridVariant = 'main' | 'page' | 'funnel' | 'custom';
|
|
16
17
|
|
|
@@ -46,13 +47,17 @@ type GridOwnProps = WithClassName & {
|
|
|
46
47
|
*/
|
|
47
48
|
autoSpanMedia?: boolean;
|
|
48
49
|
/**
|
|
49
|
-
* Masonry only. When set
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* Masonry only. When set, each `GridItem` without an explicit `columnSpan`
|
|
51
|
+
* tries to span this value; an item at the end of a row is demoted to fill
|
|
52
|
+
* just the remaining columns rather than wrapping and leaving a gap. Uses
|
|
53
|
+
* the same named span vocabulary as `columnSpan` on `GridItem`, so a value
|
|
54
|
+
* like `"half"` adapts across breakpoints via the foundation's responsive
|
|
55
|
+
* column tokens (4/8/12). A `number` is a literal column count and does
|
|
56
|
+
* **not** adapt — prefer named values unless you're using `variant="custom"`.
|
|
57
|
+
* Requires `isMasonry`; no-op otherwise. Items with an explicit `columnSpan`
|
|
58
|
+
* keep their span.
|
|
54
59
|
*/
|
|
55
|
-
masonryPreferredColumnSpan?:
|
|
60
|
+
masonryPreferredColumnSpan?: Exclude<GridItemSpanValue, 'auto'>;
|
|
56
61
|
as?: ElementType;
|
|
57
62
|
};
|
|
58
63
|
|