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.
@@ -0,0 +1,408 @@
1
+ import React, { ReactNode, CSSProperties, ComponentType } from 'react';
2
+
3
+ /**
4
+ * Definition for a card component that can be rendered in the bento grid
5
+ * Users register their card components with this interface
6
+ */
7
+ interface BentoCardDefinition<TProps = any> {
8
+ /** Unique identifier for this card type */
9
+ id: string;
10
+ /** The React component to render */
11
+ component: ComponentType<TProps>;
12
+ /** Default column span (defaults to 1) */
13
+ colSpan?: number;
14
+ /** Default row span (defaults to 1) */
15
+ rowSpan?: number;
16
+ }
17
+ /**
18
+ * Unified card definition that combines component, layout, and data mapping
19
+ * This is the preferred way to define cards - cleaner than separate arrays
20
+ * @template TData - The shape of your data source
21
+ * @template TProps - The props your card component expects
22
+ */
23
+ interface UnifiedCardDefinition<TData = unknown, TProps = any> {
24
+ /** Unique identifier for this card */
25
+ id: string;
26
+ /** The React component to render */
27
+ component: ComponentType<TProps>;
28
+ /** Function that extracts props from your data for this card */
29
+ propsSelector: (data: TData) => TProps;
30
+ /** Default column span (defaults to 1) */
31
+ colSpan?: number;
32
+ /** Default row span (defaults to 1) */
33
+ rowSpan?: number;
34
+ /** Whether this card should be visible (defaults to true) */
35
+ visible?: boolean | ((data: TData) => boolean);
36
+ /** Whether this card is in a loading state */
37
+ loading?: boolean | ((data: TData) => boolean);
38
+ /** Custom loading component for this card */
39
+ loadingComponent?: ComponentType;
40
+ }
41
+ /**
42
+ * Position and size of a card in the grid
43
+ */
44
+ interface CardPlacement {
45
+ /** Card definition ID to render */
46
+ cardId: string;
47
+ /** Starting column (1-indexed) */
48
+ col: number;
49
+ /** Starting row (1-indexed) */
50
+ row: number;
51
+ /** Number of columns to span (overrides card default) */
52
+ colSpan?: number;
53
+ /** Number of rows to span (overrides card default) */
54
+ rowSpan?: number;
55
+ }
56
+ /**
57
+ * Explicit slot-to-card mapping for preset layouts
58
+ * Allows users to specify which card goes in which slot by name
59
+ */
60
+ interface PresetSlotMapping {
61
+ /** Slot index (0-based) or slot name if preset supports named slots */
62
+ slot: number | string;
63
+ /** Card ID to place in this slot */
64
+ cardId: string;
65
+ }
66
+ /**
67
+ * Complete layout configuration for the bento grid
68
+ */
69
+ interface BentoLayoutConfig {
70
+ /** Number of columns in the grid */
71
+ columns: number;
72
+ /** Number of rows (optional - auto-calculated if not provided) */
73
+ rows?: number;
74
+ /** Gap between cards in pixels (applies to both row and column) */
75
+ gap?: number;
76
+ /** Gap between columns in pixels (overrides gap for columns) */
77
+ columnGap?: number;
78
+ /** Gap between rows in pixels (overrides gap for rows) */
79
+ rowGap?: number;
80
+ /** Card placements in the grid */
81
+ placements: CardPlacement[];
82
+ }
83
+ /**
84
+ * Breakpoint configuration for responsive layouts
85
+ */
86
+ interface BreakpointConfig {
87
+ /** Minimum viewport width for this breakpoint (in pixels) */
88
+ minWidth: number;
89
+ /** Layout to use at this breakpoint */
90
+ layout: BentoLayoutConfig | PresetLayoutName;
91
+ }
92
+ /**
93
+ * Responsive layout configuration
94
+ * Allows different layouts at different viewport widths
95
+ */
96
+ interface ResponsiveLayoutConfig {
97
+ /** Default layout (used when no breakpoint matches) */
98
+ default: BentoLayoutConfig | PresetLayoutName;
99
+ /** Breakpoint-specific layouts (applied when viewport >= minWidth) */
100
+ breakpoints: BreakpointConfig[];
101
+ }
102
+ /**
103
+ * Maps data from your data source to a specific card's props
104
+ * @template TData - The shape of your data source
105
+ */
106
+ interface CardDataMapping<TData = unknown> {
107
+ /** Card definition ID this mapping applies to */
108
+ cardId: string;
109
+ /** Function that extracts props from your data for this card */
110
+ propsSelector: (data: TData) => Record<string, unknown>;
111
+ }
112
+ /** Available preset layout names */
113
+ type PresetLayoutName = "2x2" | "3x2" | "3x3" | "4x2" | "2x1-hero-left" | "2x1-hero-right" | "dashboard-9";
114
+ /**
115
+ * Preset layout definition
116
+ */
117
+ interface PresetLayout {
118
+ name: PresetLayoutName;
119
+ columns: number;
120
+ rows: number;
121
+ /** Slot positions for cards - users just provide cardIds in order */
122
+ slots: Array<{
123
+ /** Optional name for this slot (e.g., "hero", "sidebar") */
124
+ name?: string;
125
+ col: number;
126
+ row: number;
127
+ colSpan?: number;
128
+ rowSpan?: number;
129
+ }>;
130
+ }
131
+ /**
132
+ * Props for the main BentoGrid component
133
+ * @template TData - The shape of your data source
134
+ */
135
+ interface BentoGridProps<TData = unknown> {
136
+ /** Layout configuration, preset name, or responsive config */
137
+ layout: BentoLayoutConfig | PresetLayoutName | ResponsiveLayoutConfig;
138
+ /** Registered card definitions */
139
+ cards: BentoCardDefinition[];
140
+ /** Your data source */
141
+ data: TData;
142
+ /** Maps your data to each card's props */
143
+ dataMapping: CardDataMapping<TData>[];
144
+ /** Additional CSS class for the grid container */
145
+ className?: string;
146
+ /** Additional inline styles for the grid container */
147
+ style?: CSSProperties;
148
+ /** Custom card wrapper component */
149
+ cardWrapper?: ComponentType<CardWrapperProps>;
150
+ /** Called when a card fails to render */
151
+ onCardError?: (cardId: string, error: Error) => void;
152
+ /** Accessible label for the grid region */
153
+ ariaLabel?: string;
154
+ /** ID of element that labels this grid */
155
+ ariaLabelledBy?: string;
156
+ /** Enable enter/exit animations for cards */
157
+ animated?: boolean;
158
+ /** Animation duration in milliseconds (default: 300) */
159
+ animationDuration?: number;
160
+ /** CSS class for grid cells */
161
+ cellClassName?: string | ((cardId: string) => string);
162
+ }
163
+ /**
164
+ * Props for the unified BentoGrid component (preferred API)
165
+ * Combines cards and data mapping into a single prop
166
+ * @template TData - The shape of your data source
167
+ */
168
+ interface UnifiedBentoGridProps<TData = unknown> {
169
+ /** Layout configuration, preset name, or responsive config */
170
+ layout: BentoLayoutConfig | PresetLayoutName | ResponsiveLayoutConfig;
171
+ /** Unified card definitions with components and data selectors */
172
+ cards: UnifiedCardDefinition<TData, any>[];
173
+ /** Your data source */
174
+ data: TData;
175
+ /** Additional CSS class for the grid container */
176
+ className?: string;
177
+ /** Additional inline styles for the grid container */
178
+ style?: CSSProperties;
179
+ /** Custom card wrapper component */
180
+ cardWrapper?: ComponentType<CardWrapperProps>;
181
+ /** Called when a card fails to render */
182
+ onCardError?: (cardId: string, error: Error) => void;
183
+ /** Accessible label for the grid region */
184
+ ariaLabel?: string;
185
+ /** ID of element that labels this grid */
186
+ ariaLabelledBy?: string;
187
+ /** Enable enter/exit animations for cards */
188
+ animated?: boolean;
189
+ /** Animation duration in milliseconds (default: 300) */
190
+ animationDuration?: number;
191
+ /** Global loading component for all cards */
192
+ loadingComponent?: ComponentType;
193
+ /** CSS class for grid cells */
194
+ cellClassName?: string | ((cardId: string) => string);
195
+ }
196
+ /**
197
+ * Props passed to the card wrapper component
198
+ */
199
+ interface CardWrapperProps {
200
+ children: ReactNode;
201
+ cardId: string;
202
+ className?: string;
203
+ style?: CSSProperties;
204
+ }
205
+ /**
206
+ * Props for the default BentoCard wrapper
207
+ */
208
+ interface BentoCardProps {
209
+ children: ReactNode;
210
+ className?: string;
211
+ style?: CSSProperties;
212
+ }
213
+
214
+ /**
215
+ * BentoGrid - A flexible, configurable bento grid layout system
216
+ *
217
+ * @example
218
+ * ```tsx
219
+ * <BentoGrid
220
+ * layout="3x3"
221
+ * cards={[
222
+ * { id: 'stats', component: StatsCard },
223
+ * { id: 'chart', component: ChartCard },
224
+ * ]}
225
+ * data={myData}
226
+ * dataMapping={[
227
+ * { cardId: 'stats', propsSelector: (d) => ({ count: d.total }) },
228
+ * { cardId: 'chart', propsSelector: (d) => ({ points: d.chartData }) },
229
+ * ]}
230
+ * />
231
+ * ```
232
+ */
233
+ declare function BentoGrid<TData>({ layout, cards, data, dataMapping, className, style, cardWrapper, onCardError, ariaLabel, ariaLabelledBy, animated, animationDuration, cellClassName, }: BentoGridProps<TData>): React.ReactElement;
234
+ /**
235
+ * UnifiedBentoGrid - Cleaner API that combines cards and data mapping
236
+ *
237
+ * This is the preferred way to use BentoGrid. Instead of separate `cards` and
238
+ * `dataMapping` arrays, you define everything in one place.
239
+ *
240
+ * @example
241
+ * ```tsx
242
+ * <UnifiedBentoGrid
243
+ * layout="3x2"
244
+ * cards={[
245
+ * {
246
+ * id: 'stats',
247
+ * component: StatsCard,
248
+ * propsSelector: (d) => ({ count: d.total, label: 'Users' }),
249
+ * colSpan: 2,
250
+ * },
251
+ * {
252
+ * id: 'chart',
253
+ * component: ChartCard,
254
+ * propsSelector: (d) => ({ data: d.chartData }),
255
+ * visible: (d) => d.chartData.length > 0,
256
+ * loading: (d) => d.isLoading,
257
+ * },
258
+ * ]}
259
+ * data={myData}
260
+ * animated
261
+ * />
262
+ * ```
263
+ */
264
+ declare function UnifiedBentoGrid<TData>({ layout, cards, data, className, style, cardWrapper, onCardError, ariaLabel, ariaLabelledBy, animated, animationDuration, loadingComponent: GlobalLoadingComponent, cellClassName, }: UnifiedBentoGridProps<TData>): React.ReactElement;
265
+
266
+ /**
267
+ * Default card wrapper component
268
+ * Provides basic styling - users can override with their own wrapper
269
+ * Uses CSS custom properties for easy theming
270
+ */
271
+ declare const BentoCard: React.FC<BentoCardProps>;
272
+
273
+ /**
274
+ * Check if a layout config is a responsive config
275
+ */
276
+ declare function isResponsiveConfig(layout: BentoLayoutConfig | PresetLayoutName | ResponsiveLayoutConfig): layout is ResponsiveLayoutConfig;
277
+ /**
278
+ * Hook to handle responsive layouts based on viewport width
279
+ * Returns the appropriate layout for the current viewport
280
+ *
281
+ * @example
282
+ * ```tsx
283
+ * const layout = useResponsiveLayout({
284
+ * default: "2x2",
285
+ * breakpoints: [
286
+ * { minWidth: 768, layout: "3x2" },
287
+ * { minWidth: 1024, layout: "4x2" },
288
+ * ],
289
+ * });
290
+ * ```
291
+ */
292
+ declare function useResponsiveLayout(config: BentoLayoutConfig | PresetLayoutName | ResponsiveLayoutConfig): BentoLayoutConfig | PresetLayoutName;
293
+ /**
294
+ * Hook to track window width with debouncing
295
+ */
296
+ declare function useWindowWidth(debounceMs?: number): number;
297
+ /**
298
+ * Hook to create card definitions from an object map
299
+ * Provides a cleaner API for defining cards
300
+ *
301
+ * @example
302
+ * ```tsx
303
+ * const cards = useCardDefinitions({
304
+ * stats: { component: StatsCard, colSpan: 2 },
305
+ * chart: { component: ChartCard },
306
+ * list: { component: ListCard, rowSpan: 2 },
307
+ * });
308
+ * ```
309
+ */
310
+ declare function useCardDefinitions<T extends Record<string, Omit<BentoCardDefinition<any>, "id">>>(definitions: T): BentoCardDefinition<any>[];
311
+ /**
312
+ * Hook to create data mappings from an object map
313
+ * Provides a cleaner API for mapping data to cards
314
+ *
315
+ * @example
316
+ * ```tsx
317
+ * const dataMapping = useDataMapping<MyDataType>({
318
+ * stats: (data) => ({ count: data.total, label: 'Items' }),
319
+ * chart: (data) => ({ points: data.chartData }),
320
+ * });
321
+ * ```
322
+ */
323
+ declare function useDataMapping<TData>(mappings: Record<string, (data: TData) => Record<string, unknown>>): CardDataMapping<TData>[];
324
+ /**
325
+ * Hook to create a layout configuration
326
+ * Handles both preset names and custom configs
327
+ *
328
+ * @example
329
+ * ```tsx
330
+ * // Using preset
331
+ * const layout = useLayout("3x3", ["stats", "chart", "list", ...]);
332
+ *
333
+ * // Using custom config
334
+ * const layout = useLayout({
335
+ * columns: 3,
336
+ * placements: [
337
+ * { cardId: "stats", col: 1, row: 1, colSpan: 2 },
338
+ * { cardId: "chart", col: 3, row: 1 },
339
+ * ]
340
+ * });
341
+ * ```
342
+ */
343
+ declare function useLayout(config: PresetLayoutName | BentoLayoutConfig, cardIds?: string[]): BentoLayoutConfig;
344
+
345
+ /**
346
+ * Create a simple grid layout where cards are placed sequentially
347
+ * Useful for quick prototyping
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * const layout = createSimpleLayout(3, ["a", "b", "c", "d", "e", "f"]);
352
+ * // Creates a 3-column grid with cards placed left-to-right, top-to-bottom
353
+ * ```
354
+ */
355
+ declare function createSimpleLayout(columns: number, cardIds: string[], gap?: number): BentoLayoutConfig;
356
+ /**
357
+ * Layout builder for fluent API
358
+ *
359
+ * @example
360
+ * ```ts
361
+ * const layout = layoutBuilder(3)
362
+ * .gap(16)
363
+ * .columnGap(20)
364
+ * .rowGap(12)
365
+ * .place("hero", 1, 1, { colSpan: 2, rowSpan: 2 })
366
+ * .place("stats", 3, 1)
367
+ * .place("chart", 3, 2)
368
+ * .build();
369
+ * ```
370
+ */
371
+ declare function layoutBuilder(columns: number): {
372
+ gap(value: number): /*elided*/ any;
373
+ columnGap(value: number): /*elided*/ any;
374
+ rowGap(value: number): /*elided*/ any;
375
+ rows(value: number): /*elided*/ any;
376
+ place(cardId: string, col: number, row: number, options?: {
377
+ colSpan?: number;
378
+ rowSpan?: number;
379
+ }): /*elided*/ any;
380
+ build(): BentoLayoutConfig;
381
+ };
382
+ /**
383
+ * Validate a layout configuration
384
+ * Returns array of validation errors (empty if valid)
385
+ */
386
+ declare function validateLayout(layout: BentoLayoutConfig): string[];
387
+
388
+ /**
389
+ * Preset layout definitions
390
+ * Users can use these by name instead of defining custom layouts
391
+ */
392
+ declare const PRESET_LAYOUTS: Record<PresetLayoutName, PresetLayout>;
393
+ /**
394
+ * Convert a preset name to a full layout config with card IDs
395
+ * Supports both legacy array-based assignment and explicit slot mapping
396
+ */
397
+ declare function presetToLayout(presetName: PresetLayoutName, cardIdsOrMapping: string[] | PresetSlotMapping[]): BentoLayoutConfig;
398
+ /**
399
+ * Get available slot names for a preset layout
400
+ * Useful for discovering what slots are available
401
+ */
402
+ declare function getPresetSlotNames(presetName: PresetLayoutName): string[];
403
+ /**
404
+ * Check if a value is a preset layout name
405
+ */
406
+ declare function isPresetName(value: unknown): value is PresetLayoutName;
407
+
408
+ export { BentoCard, type BentoCardDefinition, type BentoCardProps, BentoGrid, type BentoGridProps, type BentoLayoutConfig, type BreakpointConfig, type CardDataMapping, type CardPlacement, type CardWrapperProps, PRESET_LAYOUTS, type PresetLayout, type PresetLayoutName, type PresetSlotMapping, type ResponsiveLayoutConfig, UnifiedBentoGrid, type UnifiedBentoGridProps, type UnifiedCardDefinition, createSimpleLayout, getPresetSlotNames, isPresetName, isResponsiveConfig, layoutBuilder, presetToLayout, useCardDefinitions, useDataMapping, useLayout, useResponsiveLayout, useWindowWidth, validateLayout };