bento-grid-builder 0.1.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.
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/dist/index.d.mts +408 -0
- package/dist/index.d.ts +408 -0
- package/dist/index.js +800 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +748 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BentoCard: () => BentoCard,
|
|
34
|
+
BentoGrid: () => BentoGrid,
|
|
35
|
+
PRESET_LAYOUTS: () => PRESET_LAYOUTS,
|
|
36
|
+
UnifiedBentoGrid: () => UnifiedBentoGrid,
|
|
37
|
+
createSimpleLayout: () => createSimpleLayout,
|
|
38
|
+
getPresetSlotNames: () => getPresetSlotNames,
|
|
39
|
+
isPresetName: () => isPresetName,
|
|
40
|
+
isResponsiveConfig: () => isResponsiveConfig,
|
|
41
|
+
layoutBuilder: () => layoutBuilder,
|
|
42
|
+
presetToLayout: () => presetToLayout,
|
|
43
|
+
useCardDefinitions: () => useCardDefinitions,
|
|
44
|
+
useDataMapping: () => useDataMapping,
|
|
45
|
+
useLayout: () => useLayout,
|
|
46
|
+
useResponsiveLayout: () => useResponsiveLayout,
|
|
47
|
+
useWindowWidth: () => useWindowWidth,
|
|
48
|
+
validateLayout: () => validateLayout
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(index_exports);
|
|
51
|
+
|
|
52
|
+
// src/BentoGrid.tsx
|
|
53
|
+
var import_react2 = __toESM(require("react"));
|
|
54
|
+
|
|
55
|
+
// src/presets.ts
|
|
56
|
+
var PRESET_LAYOUTS = {
|
|
57
|
+
"2x2": {
|
|
58
|
+
name: "2x2",
|
|
59
|
+
columns: 2,
|
|
60
|
+
rows: 2,
|
|
61
|
+
slots: [
|
|
62
|
+
{ name: "top-left", col: 1, row: 1 },
|
|
63
|
+
{ name: "top-right", col: 2, row: 1 },
|
|
64
|
+
{ name: "bottom-left", col: 1, row: 2 },
|
|
65
|
+
{ name: "bottom-right", col: 2, row: 2 }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"3x2": {
|
|
69
|
+
name: "3x2",
|
|
70
|
+
columns: 3,
|
|
71
|
+
rows: 2,
|
|
72
|
+
slots: [
|
|
73
|
+
{ name: "top-1", col: 1, row: 1 },
|
|
74
|
+
{ name: "top-2", col: 2, row: 1 },
|
|
75
|
+
{ name: "top-3", col: 3, row: 1 },
|
|
76
|
+
{ name: "bottom-1", col: 1, row: 2 },
|
|
77
|
+
{ name: "bottom-2", col: 2, row: 2 },
|
|
78
|
+
{ name: "bottom-3", col: 3, row: 2 }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"3x3": {
|
|
82
|
+
name: "3x3",
|
|
83
|
+
columns: 3,
|
|
84
|
+
rows: 3,
|
|
85
|
+
slots: [
|
|
86
|
+
{ name: "r1-c1", col: 1, row: 1 },
|
|
87
|
+
{ name: "r1-c2", col: 2, row: 1 },
|
|
88
|
+
{ name: "r1-c3", col: 3, row: 1 },
|
|
89
|
+
{ name: "r2-c1", col: 1, row: 2 },
|
|
90
|
+
{ name: "r2-c2", col: 2, row: 2 },
|
|
91
|
+
{ name: "r2-c3", col: 3, row: 2 },
|
|
92
|
+
{ name: "r3-c1", col: 1, row: 3 },
|
|
93
|
+
{ name: "r3-c2", col: 2, row: 3 },
|
|
94
|
+
{ name: "r3-c3", col: 3, row: 3 }
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"4x2": {
|
|
98
|
+
name: "4x2",
|
|
99
|
+
columns: 4,
|
|
100
|
+
rows: 2,
|
|
101
|
+
slots: [
|
|
102
|
+
{ name: "top-1", col: 1, row: 1 },
|
|
103
|
+
{ name: "top-2", col: 2, row: 1 },
|
|
104
|
+
{ name: "top-3", col: 3, row: 1 },
|
|
105
|
+
{ name: "top-4", col: 4, row: 1 },
|
|
106
|
+
{ name: "bottom-1", col: 1, row: 2 },
|
|
107
|
+
{ name: "bottom-2", col: 2, row: 2 },
|
|
108
|
+
{ name: "bottom-3", col: 3, row: 2 },
|
|
109
|
+
{ name: "bottom-4", col: 4, row: 2 }
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"2x1-hero-left": {
|
|
113
|
+
name: "2x1-hero-left",
|
|
114
|
+
columns: 3,
|
|
115
|
+
rows: 2,
|
|
116
|
+
slots: [
|
|
117
|
+
{ name: "hero", col: 1, row: 1, colSpan: 2, rowSpan: 2 },
|
|
118
|
+
{ name: "side-top", col: 3, row: 1 },
|
|
119
|
+
{ name: "side-bottom", col: 3, row: 2 }
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"2x1-hero-right": {
|
|
123
|
+
name: "2x1-hero-right",
|
|
124
|
+
columns: 3,
|
|
125
|
+
rows: 2,
|
|
126
|
+
slots: [
|
|
127
|
+
{ name: "side-top", col: 1, row: 1 },
|
|
128
|
+
{ name: "side-bottom", col: 1, row: 2 },
|
|
129
|
+
{ name: "hero", col: 2, row: 1, colSpan: 2, rowSpan: 2 }
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"dashboard-9": {
|
|
133
|
+
name: "dashboard-9",
|
|
134
|
+
columns: 3,
|
|
135
|
+
rows: 4,
|
|
136
|
+
slots: [
|
|
137
|
+
{ name: "header-wide", col: 1, row: 1, colSpan: 2 },
|
|
138
|
+
{ name: "header-right", col: 3, row: 1 },
|
|
139
|
+
{ name: "hero", col: 1, row: 2, rowSpan: 2 },
|
|
140
|
+
{ name: "mid-center", col: 2, row: 2 },
|
|
141
|
+
{ name: "mid-right", col: 3, row: 2 },
|
|
142
|
+
{ name: "lower-center", col: 2, row: 3 },
|
|
143
|
+
{ name: "lower-right", col: 3, row: 3 },
|
|
144
|
+
{ name: "footer-left", col: 1, row: 4 },
|
|
145
|
+
{ name: "footer-wide", col: 2, row: 4, colSpan: 2 }
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function presetToLayout(presetName, cardIdsOrMapping) {
|
|
150
|
+
const preset = PRESET_LAYOUTS[presetName];
|
|
151
|
+
if (!preset) {
|
|
152
|
+
throw new Error(`Unknown preset layout: ${presetName}`);
|
|
153
|
+
}
|
|
154
|
+
let placements;
|
|
155
|
+
if (cardIdsOrMapping.length > 0 && typeof cardIdsOrMapping[0] === "object" && "slot" in cardIdsOrMapping[0]) {
|
|
156
|
+
const slotMapping = cardIdsOrMapping;
|
|
157
|
+
placements = slotMapping.map((mapping) => {
|
|
158
|
+
const slotIndex = typeof mapping.slot === "number" ? mapping.slot : preset.slots.findIndex((s) => s.name === mapping.slot);
|
|
159
|
+
if (slotIndex === -1 || slotIndex >= preset.slots.length) {
|
|
160
|
+
console.warn(
|
|
161
|
+
`Preset "${presetName}": slot "${mapping.slot}" not found`
|
|
162
|
+
);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const slot = preset.slots[slotIndex];
|
|
166
|
+
return {
|
|
167
|
+
cardId: mapping.cardId,
|
|
168
|
+
col: slot.col,
|
|
169
|
+
row: slot.row,
|
|
170
|
+
colSpan: slot.colSpan,
|
|
171
|
+
rowSpan: slot.rowSpan
|
|
172
|
+
};
|
|
173
|
+
}).filter((p) => p !== null);
|
|
174
|
+
} else {
|
|
175
|
+
const cardIds = cardIdsOrMapping;
|
|
176
|
+
placements = preset.slots.map((slot, index) => ({
|
|
177
|
+
cardId: cardIds[index] ?? `card-${index}`,
|
|
178
|
+
col: slot.col,
|
|
179
|
+
row: slot.row,
|
|
180
|
+
colSpan: slot.colSpan,
|
|
181
|
+
rowSpan: slot.rowSpan
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
columns: preset.columns,
|
|
186
|
+
rows: preset.rows,
|
|
187
|
+
placements
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function getPresetSlotNames(presetName) {
|
|
191
|
+
const preset = PRESET_LAYOUTS[presetName];
|
|
192
|
+
if (!preset) {
|
|
193
|
+
throw new Error(`Unknown preset layout: ${presetName}`);
|
|
194
|
+
}
|
|
195
|
+
return preset.slots.map((s, i) => s.name ?? `slot-${i}`);
|
|
196
|
+
}
|
|
197
|
+
function isPresetName(value) {
|
|
198
|
+
return typeof value === "string" && value in PRESET_LAYOUTS;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/BentoCard.tsx
|
|
202
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
203
|
+
var BentoCard = ({
|
|
204
|
+
children,
|
|
205
|
+
className = "",
|
|
206
|
+
style
|
|
207
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
className: `bento-card ${className}`,
|
|
211
|
+
style: {
|
|
212
|
+
backgroundColor: "var(--bento-card-bg, transparent)",
|
|
213
|
+
borderRadius: "var(--bento-card-radius, 12px)",
|
|
214
|
+
padding: "var(--bento-card-padding, 16px)",
|
|
215
|
+
border: "var(--bento-card-border, 1px solid rgba(128, 128, 128, 0.2))",
|
|
216
|
+
height: "100%",
|
|
217
|
+
boxSizing: "border-box",
|
|
218
|
+
...style
|
|
219
|
+
},
|
|
220
|
+
children
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// src/utils.ts
|
|
225
|
+
function createSimpleLayout(columns, cardIds, gap) {
|
|
226
|
+
const placements = cardIds.map((cardId, index) => ({
|
|
227
|
+
cardId,
|
|
228
|
+
col: index % columns + 1,
|
|
229
|
+
row: Math.floor(index / columns) + 1
|
|
230
|
+
}));
|
|
231
|
+
return {
|
|
232
|
+
columns,
|
|
233
|
+
gap,
|
|
234
|
+
placements
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function layoutBuilder(columns) {
|
|
238
|
+
const config = {
|
|
239
|
+
columns,
|
|
240
|
+
placements: []
|
|
241
|
+
};
|
|
242
|
+
const builder = {
|
|
243
|
+
gap(value) {
|
|
244
|
+
config.gap = value;
|
|
245
|
+
return builder;
|
|
246
|
+
},
|
|
247
|
+
columnGap(value) {
|
|
248
|
+
config.columnGap = value;
|
|
249
|
+
return builder;
|
|
250
|
+
},
|
|
251
|
+
rowGap(value) {
|
|
252
|
+
config.rowGap = value;
|
|
253
|
+
return builder;
|
|
254
|
+
},
|
|
255
|
+
rows(value) {
|
|
256
|
+
config.rows = value;
|
|
257
|
+
return builder;
|
|
258
|
+
},
|
|
259
|
+
place(cardId, col, row, options) {
|
|
260
|
+
config.placements.push({
|
|
261
|
+
cardId,
|
|
262
|
+
col,
|
|
263
|
+
row,
|
|
264
|
+
...options
|
|
265
|
+
});
|
|
266
|
+
return builder;
|
|
267
|
+
},
|
|
268
|
+
build() {
|
|
269
|
+
return config;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
return builder;
|
|
273
|
+
}
|
|
274
|
+
function validateLayout(layout) {
|
|
275
|
+
const errors = [];
|
|
276
|
+
if (layout.columns < 1) {
|
|
277
|
+
errors.push("columns must be at least 1");
|
|
278
|
+
}
|
|
279
|
+
if (layout.rows !== void 0 && layout.rows < 1) {
|
|
280
|
+
errors.push("rows must be at least 1");
|
|
281
|
+
}
|
|
282
|
+
if (layout.gap !== void 0 && layout.gap < 0) {
|
|
283
|
+
errors.push("gap cannot be negative");
|
|
284
|
+
}
|
|
285
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
286
|
+
for (const placement of layout.placements) {
|
|
287
|
+
const colSpan = placement.colSpan ?? 1;
|
|
288
|
+
const rowSpan = placement.rowSpan ?? 1;
|
|
289
|
+
if (placement.col < 1 || placement.col + colSpan - 1 > layout.columns) {
|
|
290
|
+
errors.push(
|
|
291
|
+
`Card "${placement.cardId}" exceeds column bounds (col: ${placement.col}, span: ${colSpan})`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (placement.row < 1) {
|
|
295
|
+
errors.push(
|
|
296
|
+
`Card "${placement.cardId}" has invalid row: ${placement.row}`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
for (let c = placement.col; c < placement.col + colSpan; c++) {
|
|
300
|
+
for (let r = placement.row; r < placement.row + rowSpan; r++) {
|
|
301
|
+
const key = `${c},${r}`;
|
|
302
|
+
if (occupied.has(key)) {
|
|
303
|
+
errors.push(
|
|
304
|
+
`Card "${placement.cardId}" overlaps at position (${c}, ${r})`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
occupied.add(key);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return errors;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/hooks.ts
|
|
315
|
+
var import_react = require("react");
|
|
316
|
+
function useStableValue(value) {
|
|
317
|
+
const ref = (0, import_react.useRef)(value);
|
|
318
|
+
const prevJson = (0, import_react.useRef)("");
|
|
319
|
+
const json = JSON.stringify(value, (key, val) => {
|
|
320
|
+
if (typeof val === "function") {
|
|
321
|
+
return val.toString();
|
|
322
|
+
}
|
|
323
|
+
return val;
|
|
324
|
+
});
|
|
325
|
+
if (json !== prevJson.current) {
|
|
326
|
+
ref.current = value;
|
|
327
|
+
prevJson.current = json;
|
|
328
|
+
}
|
|
329
|
+
return ref.current;
|
|
330
|
+
}
|
|
331
|
+
function isResponsiveConfig(layout) {
|
|
332
|
+
return typeof layout === "object" && "default" in layout && "breakpoints" in layout;
|
|
333
|
+
}
|
|
334
|
+
function useResponsiveLayout(config) {
|
|
335
|
+
const [width, setWidth] = (0, import_react.useState)(
|
|
336
|
+
() => typeof window !== "undefined" ? window.innerWidth : 0
|
|
337
|
+
);
|
|
338
|
+
(0, import_react.useEffect)(() => {
|
|
339
|
+
if (typeof window === "undefined") return;
|
|
340
|
+
const handleResize = () => setWidth(window.innerWidth);
|
|
341
|
+
window.addEventListener("resize", handleResize);
|
|
342
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
343
|
+
}, []);
|
|
344
|
+
return (0, import_react.useMemo)(() => {
|
|
345
|
+
if (!isResponsiveConfig(config)) {
|
|
346
|
+
return config;
|
|
347
|
+
}
|
|
348
|
+
const sortedBreakpoints = [...config.breakpoints].sort(
|
|
349
|
+
(a, b) => b.minWidth - a.minWidth
|
|
350
|
+
);
|
|
351
|
+
for (const bp of sortedBreakpoints) {
|
|
352
|
+
if (width >= bp.minWidth) {
|
|
353
|
+
return bp.layout;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return config.default;
|
|
357
|
+
}, [config, width]);
|
|
358
|
+
}
|
|
359
|
+
function useWindowWidth(debounceMs = 100) {
|
|
360
|
+
const [width, setWidth] = (0, import_react.useState)(
|
|
361
|
+
() => typeof window !== "undefined" ? window.innerWidth : 0
|
|
362
|
+
);
|
|
363
|
+
(0, import_react.useEffect)(() => {
|
|
364
|
+
if (typeof window === "undefined") return;
|
|
365
|
+
let timeoutId;
|
|
366
|
+
const handleResize = () => {
|
|
367
|
+
clearTimeout(timeoutId);
|
|
368
|
+
timeoutId = setTimeout(() => setWidth(window.innerWidth), debounceMs);
|
|
369
|
+
};
|
|
370
|
+
window.addEventListener("resize", handleResize);
|
|
371
|
+
return () => {
|
|
372
|
+
clearTimeout(timeoutId);
|
|
373
|
+
window.removeEventListener("resize", handleResize);
|
|
374
|
+
};
|
|
375
|
+
}, [debounceMs]);
|
|
376
|
+
return width;
|
|
377
|
+
}
|
|
378
|
+
function useCardDefinitions(definitions) {
|
|
379
|
+
const stableDefinitions = useStableValue(definitions);
|
|
380
|
+
return (0, import_react.useMemo)(() => {
|
|
381
|
+
return Object.entries(stableDefinitions).map(([id, def]) => ({
|
|
382
|
+
id,
|
|
383
|
+
...def
|
|
384
|
+
}));
|
|
385
|
+
}, [stableDefinitions]);
|
|
386
|
+
}
|
|
387
|
+
function useDataMapping(mappings) {
|
|
388
|
+
const stableMappings = useStableValue(mappings);
|
|
389
|
+
return (0, import_react.useMemo)(() => {
|
|
390
|
+
return Object.entries(stableMappings).map(([cardId, propsSelector]) => ({
|
|
391
|
+
cardId,
|
|
392
|
+
propsSelector
|
|
393
|
+
}));
|
|
394
|
+
}, [stableMappings]);
|
|
395
|
+
}
|
|
396
|
+
function useLayout(config, cardIds) {
|
|
397
|
+
return (0, import_react.useMemo)(() => {
|
|
398
|
+
if (isPresetName(config)) {
|
|
399
|
+
if (!cardIds) {
|
|
400
|
+
throw new Error("cardIds required when using preset layout name");
|
|
401
|
+
}
|
|
402
|
+
return presetToLayout(config, cardIds);
|
|
403
|
+
}
|
|
404
|
+
return config;
|
|
405
|
+
}, [config, cardIds]);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/BentoGrid.tsx
|
|
409
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
410
|
+
var DefaultCardWrapper = ({
|
|
411
|
+
children,
|
|
412
|
+
className,
|
|
413
|
+
style
|
|
414
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BentoCard, { className, style, children });
|
|
415
|
+
var DefaultLoadingComponent = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
416
|
+
"div",
|
|
417
|
+
{
|
|
418
|
+
className: "bento-card-loading",
|
|
419
|
+
style: {
|
|
420
|
+
width: "100%",
|
|
421
|
+
height: "100%",
|
|
422
|
+
minHeight: 100,
|
|
423
|
+
backgroundColor: "var(--bento-loading-bg, rgba(128, 128, 128, 0.1))",
|
|
424
|
+
borderRadius: "var(--bento-card-radius, 12px)",
|
|
425
|
+
animation: "bento-pulse 1.5s ease-in-out infinite"
|
|
426
|
+
},
|
|
427
|
+
"aria-label": "Loading",
|
|
428
|
+
role: "status"
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
function useGridStyles() {
|
|
432
|
+
(0, import_react2.useEffect)(() => {
|
|
433
|
+
if (typeof document === "undefined") return;
|
|
434
|
+
const styleId = "bento-grid-styles";
|
|
435
|
+
if (document.getElementById(styleId)) return;
|
|
436
|
+
const style = document.createElement("style");
|
|
437
|
+
style.id = styleId;
|
|
438
|
+
style.textContent = `
|
|
439
|
+
.bento-grid {
|
|
440
|
+
display: grid;
|
|
441
|
+
grid-template-columns: repeat(var(--bento-columns, 3), 1fr);
|
|
442
|
+
grid-template-rows: repeat(var(--bento-rows, 2), minmax(0, 1fr));
|
|
443
|
+
column-gap: var(--bento-column-gap, var(--bento-gap, 16px));
|
|
444
|
+
row-gap: var(--bento-row-gap, var(--bento-gap, 16px));
|
|
445
|
+
}
|
|
446
|
+
.bento-cell {
|
|
447
|
+
grid-column: var(--bento-col, 1) / span var(--bento-col-span, 1);
|
|
448
|
+
grid-row: var(--bento-row, 1) / span var(--bento-row-span, 1);
|
|
449
|
+
}
|
|
450
|
+
`;
|
|
451
|
+
document.head.appendChild(style);
|
|
452
|
+
}, []);
|
|
453
|
+
}
|
|
454
|
+
function useAnimationStyles() {
|
|
455
|
+
(0, import_react2.useEffect)(() => {
|
|
456
|
+
if (typeof document === "undefined") return;
|
|
457
|
+
const styleId = "bento-grid-animations";
|
|
458
|
+
if (document.getElementById(styleId)) return;
|
|
459
|
+
const style = document.createElement("style");
|
|
460
|
+
style.id = styleId;
|
|
461
|
+
style.textContent = `
|
|
462
|
+
@keyframes bento-pulse {
|
|
463
|
+
0%, 100% { opacity: 1; }
|
|
464
|
+
50% { opacity: 0.5; }
|
|
465
|
+
}
|
|
466
|
+
@keyframes bento-fade-in {
|
|
467
|
+
from { opacity: 0; transform: scale(0.95); }
|
|
468
|
+
to { opacity: 1; transform: scale(1); }
|
|
469
|
+
}
|
|
470
|
+
.bento-cell-animated {
|
|
471
|
+
animation: bento-fade-in var(--bento-animation-duration, 300ms) ease-out;
|
|
472
|
+
}
|
|
473
|
+
`;
|
|
474
|
+
document.head.appendChild(style);
|
|
475
|
+
}, []);
|
|
476
|
+
}
|
|
477
|
+
var CardErrorBoundary = class extends import_react2.default.Component {
|
|
478
|
+
constructor(props) {
|
|
479
|
+
super(props);
|
|
480
|
+
this.state = {
|
|
481
|
+
hasError: false,
|
|
482
|
+
errorKey: `${props.cardId}-${props.resetKey ?? ""}`
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
static getDerivedStateFromError() {
|
|
486
|
+
return { hasError: true };
|
|
487
|
+
}
|
|
488
|
+
static getDerivedStateFromProps(props, state) {
|
|
489
|
+
const newKey = `${props.cardId}-${props.resetKey ?? ""}`;
|
|
490
|
+
if (newKey !== state.errorKey) {
|
|
491
|
+
return { hasError: false, errorKey: newKey };
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
componentDidCatch(error) {
|
|
496
|
+
this.props.onError?.(this.props.cardId, error);
|
|
497
|
+
}
|
|
498
|
+
render() {
|
|
499
|
+
if (this.state.hasError) {
|
|
500
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
501
|
+
"div",
|
|
502
|
+
{
|
|
503
|
+
className: "bento-card-error",
|
|
504
|
+
role: "alert",
|
|
505
|
+
style: { color: "#ef4444", padding: 16 },
|
|
506
|
+
children: "Failed to render card"
|
|
507
|
+
}
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
return this.props.children;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
function BentoGrid({
|
|
514
|
+
layout,
|
|
515
|
+
cards,
|
|
516
|
+
data,
|
|
517
|
+
dataMapping,
|
|
518
|
+
className = "",
|
|
519
|
+
style,
|
|
520
|
+
cardWrapper,
|
|
521
|
+
onCardError,
|
|
522
|
+
ariaLabel,
|
|
523
|
+
ariaLabelledBy,
|
|
524
|
+
animated = false,
|
|
525
|
+
animationDuration = 300,
|
|
526
|
+
cellClassName
|
|
527
|
+
}) {
|
|
528
|
+
useGridStyles();
|
|
529
|
+
useAnimationStyles();
|
|
530
|
+
const responsiveLayout = useResponsiveLayout(layout);
|
|
531
|
+
const CardWrapper = cardWrapper ?? DefaultCardWrapper;
|
|
532
|
+
const resolvedLayout = (0, import_react2.useMemo)(() => {
|
|
533
|
+
if (isPresetName(responsiveLayout)) {
|
|
534
|
+
const cardIds = dataMapping.map((m) => m.cardId);
|
|
535
|
+
return presetToLayout(responsiveLayout, cardIds);
|
|
536
|
+
}
|
|
537
|
+
return responsiveLayout;
|
|
538
|
+
}, [responsiveLayout, dataMapping]);
|
|
539
|
+
const cardMap = (0, import_react2.useMemo)(() => {
|
|
540
|
+
const map = /* @__PURE__ */ new Map();
|
|
541
|
+
cards.forEach((card) => map.set(card.id, card));
|
|
542
|
+
return map;
|
|
543
|
+
}, [cards]);
|
|
544
|
+
const dataMappingMap = (0, import_react2.useMemo)(() => {
|
|
545
|
+
const map = /* @__PURE__ */ new Map();
|
|
546
|
+
dataMapping.forEach((mapping) => map.set(mapping.cardId, mapping));
|
|
547
|
+
return map;
|
|
548
|
+
}, [dataMapping]);
|
|
549
|
+
(0, import_react2.useMemo)(() => {
|
|
550
|
+
if (process.env.NODE_ENV !== "production") {
|
|
551
|
+
const errors = validateLayout(resolvedLayout);
|
|
552
|
+
if (errors.length > 0) {
|
|
553
|
+
console.warn("BentoGrid layout validation errors:", errors);
|
|
554
|
+
}
|
|
555
|
+
for (const placement of resolvedLayout.placements) {
|
|
556
|
+
if (!cards.some((c) => c.id === placement.cardId)) {
|
|
557
|
+
console.warn(
|
|
558
|
+
`BentoGrid: Card "${placement.cardId}" in layout but not in cards array`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
for (const placement of resolvedLayout.placements) {
|
|
563
|
+
if (!dataMapping.some((m) => m.cardId === placement.cardId)) {
|
|
564
|
+
console.warn(
|
|
565
|
+
`BentoGrid: Card "${placement.cardId}" has no data mapping`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}, [resolvedLayout, cards, dataMapping]);
|
|
571
|
+
const {
|
|
572
|
+
columns,
|
|
573
|
+
rows,
|
|
574
|
+
gap = 16,
|
|
575
|
+
columnGap,
|
|
576
|
+
rowGap,
|
|
577
|
+
placements
|
|
578
|
+
} = resolvedLayout;
|
|
579
|
+
const calculatedRows = rows ?? Math.max(...placements.map((p) => p.row + (p.rowSpan ?? 1) - 1), 1);
|
|
580
|
+
const gridStyle = {
|
|
581
|
+
"--bento-columns": columns,
|
|
582
|
+
"--bento-rows": calculatedRows,
|
|
583
|
+
"--bento-gap": `${gap}px`,
|
|
584
|
+
"--bento-column-gap": `${columnGap ?? gap}px`,
|
|
585
|
+
"--bento-row-gap": `${rowGap ?? gap}px`,
|
|
586
|
+
...animated && { "--bento-animation-duration": `${animationDuration}ms` },
|
|
587
|
+
...style
|
|
588
|
+
};
|
|
589
|
+
const getCellClassName = (cardId) => {
|
|
590
|
+
const baseClass = `bento-cell ${animated ? "bento-cell-animated" : ""}`;
|
|
591
|
+
if (!cellClassName) return baseClass;
|
|
592
|
+
if (typeof cellClassName === "function")
|
|
593
|
+
return `${baseClass} ${cellClassName(cardId)}`;
|
|
594
|
+
return `${baseClass} ${cellClassName}`;
|
|
595
|
+
};
|
|
596
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
597
|
+
"div",
|
|
598
|
+
{
|
|
599
|
+
className: `bento-grid ${className}`,
|
|
600
|
+
style: gridStyle,
|
|
601
|
+
role: "region",
|
|
602
|
+
"aria-label": ariaLabel ?? (ariaLabelledBy ? void 0 : "Dashboard grid"),
|
|
603
|
+
"aria-labelledby": ariaLabelledBy,
|
|
604
|
+
children: placements.map((placement) => {
|
|
605
|
+
const card = cardMap.get(placement.cardId);
|
|
606
|
+
const mapping = dataMappingMap.get(placement.cardId);
|
|
607
|
+
if (!card) {
|
|
608
|
+
console.warn(`BentoGrid: Card "${placement.cardId}" not found`);
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
const cardProps = mapping ? mapping.propsSelector(data) : {};
|
|
612
|
+
const resetKey = JSON.stringify(cardProps);
|
|
613
|
+
const colSpan = placement.colSpan ?? card.colSpan ?? 1;
|
|
614
|
+
const rowSpan = placement.rowSpan ?? card.rowSpan ?? 1;
|
|
615
|
+
const cellStyle = {
|
|
616
|
+
"--bento-col": placement.col,
|
|
617
|
+
"--bento-row": placement.row,
|
|
618
|
+
"--bento-col-span": colSpan,
|
|
619
|
+
"--bento-row-span": rowSpan
|
|
620
|
+
};
|
|
621
|
+
const CardComponent = card.component;
|
|
622
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
623
|
+
"div",
|
|
624
|
+
{
|
|
625
|
+
className: getCellClassName(placement.cardId),
|
|
626
|
+
style: cellStyle,
|
|
627
|
+
"data-card-id": placement.cardId,
|
|
628
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
629
|
+
CardErrorBoundary,
|
|
630
|
+
{
|
|
631
|
+
cardId: placement.cardId,
|
|
632
|
+
onError: onCardError,
|
|
633
|
+
resetKey,
|
|
634
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CardWrapper, { cardId: placement.cardId, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CardComponent, { ...cardProps }) })
|
|
635
|
+
}
|
|
636
|
+
)
|
|
637
|
+
},
|
|
638
|
+
`${placement.cardId}-${placement.col}-${placement.row}`
|
|
639
|
+
);
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
function isCardVisible(card, data) {
|
|
645
|
+
if (card.visible === void 0) return true;
|
|
646
|
+
if (typeof card.visible === "function") return card.visible(data);
|
|
647
|
+
return card.visible;
|
|
648
|
+
}
|
|
649
|
+
function isCardLoading(card, data) {
|
|
650
|
+
if (card.loading === void 0) return false;
|
|
651
|
+
if (typeof card.loading === "function") return card.loading(data);
|
|
652
|
+
return card.loading;
|
|
653
|
+
}
|
|
654
|
+
function UnifiedBentoGrid({
|
|
655
|
+
layout,
|
|
656
|
+
cards,
|
|
657
|
+
data,
|
|
658
|
+
className = "",
|
|
659
|
+
style,
|
|
660
|
+
cardWrapper,
|
|
661
|
+
onCardError,
|
|
662
|
+
ariaLabel,
|
|
663
|
+
ariaLabelledBy,
|
|
664
|
+
animated = false,
|
|
665
|
+
animationDuration = 300,
|
|
666
|
+
loadingComponent: GlobalLoadingComponent = DefaultLoadingComponent,
|
|
667
|
+
cellClassName
|
|
668
|
+
}) {
|
|
669
|
+
useGridStyles();
|
|
670
|
+
useAnimationStyles();
|
|
671
|
+
const responsiveLayout = useResponsiveLayout(layout);
|
|
672
|
+
const CardWrapper = cardWrapper ?? DefaultCardWrapper;
|
|
673
|
+
const visibleCards = (0, import_react2.useMemo)(() => {
|
|
674
|
+
return cards.filter((card) => isCardVisible(card, data));
|
|
675
|
+
}, [cards, data]);
|
|
676
|
+
const resolvedLayout = (0, import_react2.useMemo)(() => {
|
|
677
|
+
if (isPresetName(responsiveLayout)) {
|
|
678
|
+
const cardIds = visibleCards.map((c) => c.id);
|
|
679
|
+
return presetToLayout(responsiveLayout, cardIds);
|
|
680
|
+
}
|
|
681
|
+
return responsiveLayout;
|
|
682
|
+
}, [responsiveLayout, visibleCards]);
|
|
683
|
+
const cardMap = (0, import_react2.useMemo)(() => {
|
|
684
|
+
const map = /* @__PURE__ */ new Map();
|
|
685
|
+
visibleCards.forEach((card) => map.set(card.id, card));
|
|
686
|
+
return map;
|
|
687
|
+
}, [visibleCards]);
|
|
688
|
+
(0, import_react2.useMemo)(() => {
|
|
689
|
+
if (process.env.NODE_ENV !== "production") {
|
|
690
|
+
const errors = validateLayout(resolvedLayout);
|
|
691
|
+
if (errors.length > 0) {
|
|
692
|
+
console.warn("UnifiedBentoGrid layout validation errors:", errors);
|
|
693
|
+
}
|
|
694
|
+
for (const placement of resolvedLayout.placements) {
|
|
695
|
+
if (!visibleCards.some((c) => c.id === placement.cardId)) {
|
|
696
|
+
console.warn(
|
|
697
|
+
`UnifiedBentoGrid: Card "${placement.cardId}" in layout but not in cards array`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}, [resolvedLayout, visibleCards]);
|
|
703
|
+
const {
|
|
704
|
+
columns,
|
|
705
|
+
rows,
|
|
706
|
+
gap = 16,
|
|
707
|
+
columnGap,
|
|
708
|
+
rowGap,
|
|
709
|
+
placements
|
|
710
|
+
} = resolvedLayout;
|
|
711
|
+
const calculatedRows = rows ?? Math.max(...placements.map((p) => p.row + (p.rowSpan ?? 1) - 1), 1);
|
|
712
|
+
const gridStyle = {
|
|
713
|
+
"--bento-columns": columns,
|
|
714
|
+
"--bento-rows": calculatedRows,
|
|
715
|
+
"--bento-gap": `${gap}px`,
|
|
716
|
+
"--bento-column-gap": `${columnGap ?? gap}px`,
|
|
717
|
+
"--bento-row-gap": `${rowGap ?? gap}px`,
|
|
718
|
+
...animated && { "--bento-animation-duration": `${animationDuration}ms` },
|
|
719
|
+
...style
|
|
720
|
+
};
|
|
721
|
+
const getCellClassName = (cardId) => {
|
|
722
|
+
const baseClass = `bento-cell ${animated ? "bento-cell-animated" : ""}`;
|
|
723
|
+
if (!cellClassName) return baseClass;
|
|
724
|
+
if (typeof cellClassName === "function")
|
|
725
|
+
return `${baseClass} ${cellClassName(cardId)}`;
|
|
726
|
+
return `${baseClass} ${cellClassName}`;
|
|
727
|
+
};
|
|
728
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
className: `bento-grid ${className}`,
|
|
732
|
+
style: gridStyle,
|
|
733
|
+
role: "region",
|
|
734
|
+
"aria-label": ariaLabel ?? (ariaLabelledBy ? void 0 : "Dashboard grid"),
|
|
735
|
+
"aria-labelledby": ariaLabelledBy,
|
|
736
|
+
children: placements.map((placement) => {
|
|
737
|
+
const card = cardMap.get(placement.cardId);
|
|
738
|
+
if (!card) {
|
|
739
|
+
if (process.env.NODE_ENV !== "production") {
|
|
740
|
+
console.warn(
|
|
741
|
+
`UnifiedBentoGrid: Card "${placement.cardId}" not found`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
const loading = isCardLoading(card, data);
|
|
747
|
+
const cardProps = card.propsSelector(data);
|
|
748
|
+
const resetKey = JSON.stringify(cardProps);
|
|
749
|
+
const colSpan = placement.colSpan ?? card.colSpan ?? 1;
|
|
750
|
+
const rowSpan = placement.rowSpan ?? card.rowSpan ?? 1;
|
|
751
|
+
const cellStyle = {
|
|
752
|
+
"--bento-col": placement.col,
|
|
753
|
+
"--bento-row": placement.row,
|
|
754
|
+
"--bento-col-span": colSpan,
|
|
755
|
+
"--bento-row-span": rowSpan
|
|
756
|
+
};
|
|
757
|
+
const CardComponent = card.component;
|
|
758
|
+
const LoadingComponent = card.loadingComponent ?? GlobalLoadingComponent;
|
|
759
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
760
|
+
"div",
|
|
761
|
+
{
|
|
762
|
+
className: getCellClassName(placement.cardId),
|
|
763
|
+
style: cellStyle,
|
|
764
|
+
"data-card-id": placement.cardId,
|
|
765
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
766
|
+
CardErrorBoundary,
|
|
767
|
+
{
|
|
768
|
+
cardId: placement.cardId,
|
|
769
|
+
onError: onCardError,
|
|
770
|
+
resetKey,
|
|
771
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CardWrapper, { cardId: placement.cardId, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingComponent, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CardComponent, { ...cardProps }) })
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
},
|
|
775
|
+
`${placement.cardId}-${placement.col}-${placement.row}`
|
|
776
|
+
);
|
|
777
|
+
})
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
782
|
+
0 && (module.exports = {
|
|
783
|
+
BentoCard,
|
|
784
|
+
BentoGrid,
|
|
785
|
+
PRESET_LAYOUTS,
|
|
786
|
+
UnifiedBentoGrid,
|
|
787
|
+
createSimpleLayout,
|
|
788
|
+
getPresetSlotNames,
|
|
789
|
+
isPresetName,
|
|
790
|
+
isResponsiveConfig,
|
|
791
|
+
layoutBuilder,
|
|
792
|
+
presetToLayout,
|
|
793
|
+
useCardDefinitions,
|
|
794
|
+
useDataMapping,
|
|
795
|
+
useLayout,
|
|
796
|
+
useResponsiveLayout,
|
|
797
|
+
useWindowWidth,
|
|
798
|
+
validateLayout
|
|
799
|
+
});
|
|
800
|
+
//# sourceMappingURL=index.js.map
|