@boostdev/design-system-components 2.2.0 → 2.3.0

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.
@@ -9,12 +9,14 @@ function BdsGrid({
9
9
  columns,
10
10
  isCentered,
11
11
  isMasonry,
12
+ masonryPreferredColumnSpan,
12
13
  children,
13
14
  }: {
14
15
  variant?: GridVariant;
15
16
  columns?: number;
16
17
  isCentered?: boolean;
17
18
  isMasonry?: boolean;
19
+ masonryPreferredColumnSpan?: number;
18
20
  children?: React.ReactNode;
19
21
  }) {
20
22
  return React.createElement(
@@ -24,6 +26,7 @@ function BdsGrid({
24
26
  columns,
25
27
  'is-centered': isCentered || undefined,
26
28
  'is-masonry': isMasonry || undefined,
29
+ 'masonry-preferred-column-span': masonryPreferredColumnSpan,
27
30
  },
28
31
  children,
29
32
  );
@@ -45,7 +48,7 @@ function Item({
45
48
  return React.createElement(
46
49
  'bds-grid-item',
47
50
  {
48
- 'column-span': columnSpan ?? 'full',
51
+ 'column-span': columnSpan,
49
52
  'start-column': startColumn,
50
53
  'end-column': endColumn,
51
54
  'row-span': rowSpan,
@@ -71,6 +74,7 @@ const meta = {
71
74
  columns: { control: 'number' },
72
75
  isCentered: { control: 'boolean' },
73
76
  isMasonry: { control: 'boolean' },
77
+ masonryPreferredColumnSpan: { control: { type: 'number', min: 1, max: 4 } },
74
78
  },
75
79
  } satisfies Meta<typeof BdsGrid>;
76
80
 
@@ -189,6 +193,43 @@ export const MasonryWithSpans: Story = {
189
193
  ),
190
194
  };
191
195
 
196
+ /**
197
+ * `masonry-preferred-column-span="2"` makes every auto-span item span 2 columns.
198
+ * With 7 items in a 12-column desktop grid, the 7th would normally leave a trailing
199
+ * gap — the polyfill **demotes** its span to fill just the remaining columns.
200
+ *
201
+ * Flip `masonryPreferredColumnSpan` between 1–4 in the Controls to see the
202
+ * packing re-run. Resize the viewport to watch the demotion adapt to the
203
+ * current column count.
204
+ */
205
+ export const MasonryPreferredColumnSpan: Story = {
206
+ args: { isMasonry: true, masonryPreferredColumnSpan: 2 },
207
+ render: (args) => (
208
+ <BdsGrid {...args}>
209
+ {Array.from({ length: 7 }).map((_, i) => (
210
+ <Item key={i}><div style={masonryCellStyle(i)}>Card {i + 1}</div></Item>
211
+ ))}
212
+ </BdsGrid>
213
+ ),
214
+ };
215
+
216
+ /**
217
+ * An explicit `column-span="three-quarters"` hero alongside auto items.
218
+ * The hero keeps its span; its columns consume the demotion budget, so the
219
+ * auto items fill the remaining row consistently.
220
+ */
221
+ export const MasonryPreferredColumnSpanWithHero: Story = {
222
+ args: { isMasonry: true, masonryPreferredColumnSpan: 2 },
223
+ render: (args) => (
224
+ <BdsGrid {...args}>
225
+ <Item columnSpan="three-quarters"><div style={masonryCellStyle(0)}>Hero (three-quarters)</div></Item>
226
+ {Array.from({ length: 8 }).map((_, i) => (
227
+ <Item key={i}><div style={masonryCellStyle(i + 1)}>Card {i + 1}</div></Item>
228
+ ))}
229
+ </BdsGrid>
230
+ ),
231
+ };
232
+
192
233
  export const ComplexLayout: Story = {
193
234
  args: {},
194
235
  render: () => (
@@ -99,4 +99,18 @@ describe('bds-grid-item', () => {
99
99
  expect(item.style.gridColumn).toBe('span var(--bds-grid_columns-33)');
100
100
  cleanup(grid);
101
101
  });
102
+
103
+ it('marks data-column-span-auto when column-span is not set', async () => {
104
+ const el = (await fixture('<bds-grid-item></bds-grid-item>')) as BdsGridItem;
105
+ expect(el.hasAttribute('data-column-span-auto')).toBe(true);
106
+ cleanup(el);
107
+ });
108
+
109
+ it('clears data-column-span-auto when column-span is set', async () => {
110
+ const el = (await fixture(
111
+ '<bds-grid-item column-span="half"></bds-grid-item>',
112
+ )) as BdsGridItem;
113
+ expect(el.hasAttribute('data-column-span-auto')).toBe(false);
114
+ cleanup(el);
115
+ });
102
116
  });
@@ -161,6 +161,13 @@ export class BdsGridItem extends LitElement {
161
161
 
162
162
  this.style.gridColumn = gridColumn;
163
163
  this.style.gridRow = this.rowSpan !== undefined ? `span ${this.rowSpan}` : '';
164
+
165
+ // Marker the masonry polyfill reads to know this item's span wasn't set by
166
+ // the consumer — it may override with the parent `masonry-preferred-column-span`.
167
+ const isColumnSpanAuto =
168
+ (this.columnSpan === undefined || this.columnSpan === null) && !hasExplicitRange;
169
+ if (isColumnSpanAuto) this.setAttribute('data-column-span-auto', '');
170
+ else this.removeAttribute('data-column-span-auto');
164
171
  }
165
172
 
166
173
  render() {
@@ -59,4 +59,12 @@ describe('bds-grid', () => {
59
59
  expect(el.shadowRoot!.querySelector('.grid')!.classList.contains('--masonry')).toBe(true);
60
60
  cleanup(el);
61
61
  });
62
+
63
+ it('reflects masonry-preferred-column-span as a number property', async () => {
64
+ const el = await fixture(
65
+ '<bds-grid is-masonry masonry-preferred-column-span="2"></bds-grid>',
66
+ ) as BdsGrid;
67
+ expect(el.masonryPreferredColumnSpan).toBe(2);
68
+ cleanup(el);
69
+ });
62
70
  });
@@ -16,6 +16,9 @@ export type GridVariant = 'main' | 'page' | 'funnel' | 'custom';
16
16
  * auto-span-media — boolean; child `<bds-grid-item>`s without a `column-span`
17
17
  * attribute resolve their span from the intrinsic aspect
18
18
  * ratio of their first `<img>`/`<video>` child.
19
+ * masonry-preferred-column-span — number; masonry only. Child items without a
20
+ * `column-span` attribute try to span this many columns; an
21
+ * item at row-end is demoted to the remaining column count.
19
22
  *
20
23
  * Slots:
21
24
  * (default) — grid content; typically `<bds-grid-item>` children
@@ -76,6 +79,11 @@ export class BdsGrid extends LitElement {
76
79
  isCentered: { type: Boolean, attribute: 'is-centered', reflect: true },
77
80
  isMasonry: { type: Boolean, attribute: 'is-masonry', reflect: true },
78
81
  autoSpanMedia: { type: Boolean, attribute: 'auto-span-media', reflect: true },
82
+ masonryPreferredColumnSpan: {
83
+ type: Number,
84
+ attribute: 'masonry-preferred-column-span',
85
+ reflect: true,
86
+ },
79
87
  };
80
88
 
81
89
  declare variant: GridVariant;
@@ -83,6 +91,7 @@ export class BdsGrid extends LitElement {
83
91
  declare isCentered: boolean;
84
92
  declare isMasonry: boolean;
85
93
  declare autoSpanMedia: boolean;
94
+ declare masonryPreferredColumnSpan: number | undefined;
86
95
 
87
96
  private _ro?: ResizeObserver;
88
97
  private _mo?: MutationObserver;
@@ -95,6 +104,7 @@ export class BdsGrid extends LitElement {
95
104
  this.isCentered = false;
96
105
  this.isMasonry = false;
97
106
  this.autoSpanMedia = false;
107
+ this.masonryPreferredColumnSpan = undefined;
98
108
  }
99
109
 
100
110
  disconnectedCallback() {
@@ -144,7 +154,11 @@ export class BdsGrid extends LitElement {
144
154
  cancelAnimationFrame(this._frame);
145
155
  this._frame = requestAnimationFrame(() => {
146
156
  const container = this._gridEl();
147
- if (container) applyMasonryLayout(container, this._items());
157
+ if (container) {
158
+ applyMasonryLayout(container, this._items(), {
159
+ preferredColumnSpan: this.masonryPreferredColumnSpan,
160
+ });
161
+ }
148
162
  });
149
163
  }
150
164