@papernote/ui 1.1.0 → 1.2.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.
Files changed (75) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +455 -455
  3. package/dist/components/CurrencyInput.d.ts +52 -0
  4. package/dist/components/CurrencyInput.d.ts.map +1 -0
  5. package/dist/components/DataTable.d.ts +3 -1
  6. package/dist/components/DataTable.d.ts.map +1 -1
  7. package/dist/components/Modal.d.ts.map +1 -1
  8. package/dist/components/Page.d.ts +2 -0
  9. package/dist/components/Page.d.ts.map +1 -1
  10. package/dist/components/PageLayout.d.ts +5 -1
  11. package/dist/components/PageLayout.d.ts.map +1 -1
  12. package/dist/components/index.d.ts +4 -0
  13. package/dist/components/index.d.ts.map +1 -1
  14. package/dist/index.d.ts +204 -4
  15. package/dist/index.esm.js +415 -88
  16. package/dist/index.esm.js.map +1 -1
  17. package/dist/index.js +413 -82
  18. package/dist/index.js.map +1 -1
  19. package/dist/styles.css +2877 -2675
  20. package/dist/utils/excelExport.d.ts +143 -0
  21. package/dist/utils/excelExport.d.ts.map +1 -0
  22. package/dist/utils/index.d.ts +2 -0
  23. package/dist/utils/index.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/AdminModal.css +49 -49
  26. package/src/components/CurrencyInput.stories.tsx +290 -0
  27. package/src/components/CurrencyInput.tsx +193 -0
  28. package/src/components/DataTable.tsx +78 -14
  29. package/src/components/Modal.stories.tsx +64 -0
  30. package/src/components/Modal.tsx +15 -2
  31. package/src/components/Page.stories.tsx +76 -0
  32. package/src/components/Page.tsx +35 -3
  33. package/src/components/PageLayout.stories.tsx +75 -0
  34. package/src/components/PageLayout.tsx +28 -9
  35. package/src/components/RoleManager.css +10 -10
  36. package/src/components/Spreadsheet.css +216 -216
  37. package/src/components/Spreadsheet.stories.tsx +362 -362
  38. package/src/components/Spreadsheet.tsx +351 -351
  39. package/src/components/SpreadsheetSimple.stories.tsx +27 -27
  40. package/src/components/Tabs.tsx +152 -152
  41. package/src/components/index.ts +5 -0
  42. package/src/styles/index.css +41 -4
  43. package/src/utils/excelExport.stories.tsx +535 -0
  44. package/src/utils/excelExport.ts +225 -0
  45. package/src/utils/index.ts +3 -0
  46. package/tailwind.config.js +253 -253
  47. package/dist/components/Button.stories.d.ts +0 -51
  48. package/dist/components/Button.stories.d.ts.map +0 -1
  49. package/dist/components/ChartVisualizationUI.d.ts +0 -21
  50. package/dist/components/ChartVisualizationUI.d.ts.map +0 -1
  51. package/dist/components/ChatUI.d.ts +0 -23
  52. package/dist/components/ChatUI.d.ts.map +0 -1
  53. package/dist/components/CommissionDashboardUI.d.ts +0 -25
  54. package/dist/components/CommissionDashboardUI.d.ts.map +0 -1
  55. package/dist/components/DataTable.stories.d.ts +0 -23
  56. package/dist/components/DataTable.stories.d.ts.map +0 -1
  57. package/dist/components/FormField.d.ts +0 -35
  58. package/dist/components/FormField.d.ts.map +0 -1
  59. package/dist/components/Input.stories.d.ts +0 -366
  60. package/dist/components/Input.stories.d.ts.map +0 -1
  61. package/dist/components/InsightsPanelUI.d.ts +0 -21
  62. package/dist/components/InsightsPanelUI.d.ts.map +0 -1
  63. package/dist/components/PaymentHistoryTimeline.d.ts +0 -34
  64. package/dist/components/PaymentHistoryTimeline.d.ts.map +0 -1
  65. package/dist/components/RelationshipManagerUI.d.ts +0 -60
  66. package/dist/components/RelationshipManagerUI.d.ts.map +0 -1
  67. package/dist/components/RoleManager.d.ts +0 -19
  68. package/dist/components/RoleManager.d.ts.map +0 -1
  69. package/dist/components/SplitCommissionBadge.d.ts +0 -18
  70. package/dist/components/SplitCommissionBadge.d.ts.map +0 -1
  71. package/dist/components/Spreadsheet.css +0 -216
  72. package/dist/components/__tests__/Button.test.d.ts +0 -2
  73. package/dist/components/__tests__/Button.test.d.ts.map +0 -1
  74. package/dist/components/__tests__/Input.test.d.ts +0 -2
  75. package/dist/components/__tests__/Input.test.d.ts.map +0 -1
@@ -1,351 +1,351 @@
1
- import React, { useState, useCallback } from 'react';
2
- import BaseSpreadsheet, { CellBase, Matrix } from 'react-spreadsheet';
3
- import { read, utils, writeFile, WorkBook } from 'xlsx';
4
- import Button from './Button';
5
- import Card, { CardHeader, CardTitle, CardContent } from './Card';
6
- import Stack from './Stack';
7
- import { Download, Upload, Save } from 'lucide-react';
8
- import { addSuccessMessage, addErrorMessage } from './StatusBar';
9
- import './Spreadsheet.css';
10
-
11
- // Re-export types for external use
12
- export type { CellBase, Matrix } from 'react-spreadsheet';
13
-
14
- /**
15
- * Enhanced cell type with formula support
16
- */
17
- export interface SpreadsheetCell extends CellBase {
18
- value: string | number | boolean;
19
- formula?: string;
20
- readOnly?: boolean;
21
- className?: string;
22
- }
23
-
24
- /**
25
- * Spreadsheet component props
26
- */
27
- export interface SpreadsheetProps {
28
- /** Initial data matrix */
29
- data?: Matrix<SpreadsheetCell>;
30
- /** Callback when data changes */
31
- onChange?: (data: Matrix<SpreadsheetCell>) => void;
32
- /** Number of rows to display */
33
- rows?: number;
34
- /** Number of columns to display */
35
- columns?: number;
36
- /** Column labels (A, B, C... if not provided) */
37
- columnLabels?: string[];
38
- /** Row labels (1, 2, 3... if not provided) */
39
- rowLabels?: string[];
40
- /** Show toolbar with actions */
41
- showToolbar?: boolean;
42
- /** Enable Excel import */
43
- enableImport?: boolean;
44
- /** Enable Excel export */
45
- enableExport?: boolean;
46
- /** Enable save button */
47
- enableSave?: boolean;
48
- /** Save handler */
49
- onSave?: (data: Matrix<SpreadsheetCell>) => Promise<void> | void;
50
- /** Title to display in toolbar */
51
- title?: string;
52
- /** Additional toolbar actions */
53
- actions?: React.ReactNode;
54
- /** Wrap in Card component */
55
- wrapInCard?: boolean;
56
- /** Custom className for the spreadsheet container */
57
- className?: string;
58
- /** Make entire spreadsheet read-only */
59
- readOnly?: boolean;
60
- /** Default export filename */
61
- exportFileName?: string;
62
- }
63
-
64
- /**
65
- * Spreadsheet - Interactive spreadsheet component with formula support
66
- *
67
- * A full-featured spreadsheet component for report designers and data editing.
68
- * Built on react-spreadsheet with Fast Formula Parser (280+ Excel formulas).
69
- *
70
- * **Features:**
71
- * - Excel-like formula support (SUM, AVERAGE, VLOOKUP, IF, etc.)
72
- * - Excel import/export with SheetJS
73
- * - Save/load functionality
74
- * - Keyboard navigation
75
- * - Copy/paste support
76
- * - Customizable styling to match notebook-ui aesthetic
77
- *
78
- * @example
79
- * ```tsx
80
- * // Basic spreadsheet
81
- * <Spreadsheet
82
- * rows={10}
83
- * columns={5}
84
- * showToolbar
85
- * />
86
- *
87
- * // Report designer with formulas
88
- * const [reportData, setReportData] = useState<Matrix<SpreadsheetCell>>([
89
- * [{ value: 'Q1' }, { value: 100 }],
90
- * [{ value: 'Q2' }, { value: 200 }],
91
- * [{ value: 'Total' }, { formula: '=SUM(B1:B2)' }],
92
- * ]);
93
- *
94
- * <Spreadsheet
95
- * data={reportData}
96
- * onChange={setReportData}
97
- * title="Sales Report"
98
- * showToolbar
99
- * enableImport
100
- * enableExport
101
- * enableSave
102
- * onSave={async (data) => {
103
- * await saveReport(data);
104
- * }}
105
- * />
106
- *
107
- * // With custom actions
108
- * <Spreadsheet
109
- * data={data}
110
- * onChange={setData}
111
- * showToolbar
112
- * actions={
113
- * <Button onClick={handleCustomAction}>Custom Action</Button>
114
- * }
115
- * />
116
- * ```
117
- */
118
- export const Spreadsheet: React.FC<SpreadsheetProps> = ({
119
- data: initialData,
120
- onChange,
121
- rows = 20,
122
- columns = 10,
123
- columnLabels,
124
- rowLabels,
125
- showToolbar = false,
126
- enableImport = false,
127
- enableExport = false,
128
- enableSave = false,
129
- onSave,
130
- title,
131
- actions,
132
- wrapInCard = false,
133
- className = '',
134
- readOnly = false,
135
- exportFileName = 'spreadsheet.xlsx',
136
- }) => {
137
- // Initialize data if not provided
138
- const [data, setData] = useState<Matrix<SpreadsheetCell>>(() => {
139
- if (initialData) return initialData;
140
- return Array(rows)
141
- .fill(null)
142
- .map(() => Array(columns).fill(null).map(() => ({ value: '' })));
143
- });
144
-
145
- const [isSaving, setIsSaving] = useState(false);
146
-
147
- // Handle data changes
148
- const handleChange = useCallback(
149
- (newData: Matrix<SpreadsheetCell>) => {
150
- setData(newData);
151
- onChange?.(newData);
152
- },
153
- [onChange]
154
- );
155
-
156
- // Handle Excel import
157
- const handleImport = useCallback(
158
- (event: React.ChangeEvent<HTMLInputElement>) => {
159
- const file = event.target.files?.[0];
160
- if (!file) return;
161
-
162
- const reader = new FileReader();
163
- reader.onload = (e) => {
164
- try {
165
- const workbook: WorkBook = read(e.target?.result, { type: 'binary' });
166
- const sheetName = workbook.SheetNames[0];
167
- const worksheet = workbook.Sheets[sheetName];
168
-
169
- // Convert to array of arrays
170
- const jsonData: any[][] = utils.sheet_to_json(worksheet, { header: 1 });
171
-
172
- // Convert to spreadsheet format
173
- const spreadsheetData: Matrix<SpreadsheetCell> = jsonData.map(row =>
174
- row.map(cell => ({
175
- value: cell,
176
- }))
177
- );
178
-
179
- handleChange(spreadsheetData);
180
- addSuccessMessage('Excel file imported successfully');
181
- } catch (error) {
182
- console.error('Error importing Excel file:', error);
183
- addErrorMessage('Failed to import Excel file');
184
- }
185
- };
186
- reader.readAsBinaryString(file);
187
-
188
- // Reset input
189
- event.target.value = '';
190
- },
191
- [handleChange]
192
- );
193
-
194
- // Handle Excel export
195
- const handleExport = useCallback(() => {
196
- try {
197
- // Convert spreadsheet data to worksheet format
198
- const worksheetData = data.map(row =>
199
- row.map(cell => {
200
- // If cell has a formula, export the calculated value
201
- if (cell?.formula) {
202
- return cell.value ?? cell.formula;
203
- }
204
- return cell?.value ?? '';
205
- })
206
- );
207
-
208
- const worksheet = utils.aoa_to_sheet(worksheetData);
209
- const workbook = utils.book_new();
210
- utils.book_append_sheet(workbook, worksheet, 'Sheet1');
211
-
212
- writeFile(workbook, exportFileName);
213
- addSuccessMessage('Excel file exported successfully');
214
- } catch (error) {
215
- console.error('Error exporting Excel file:', error);
216
- addErrorMessage('Failed to export Excel file');
217
- }
218
- }, [data, exportFileName]);
219
-
220
- // Handle save
221
- const handleSave = useCallback(async () => {
222
- if (!onSave) return;
223
-
224
- setIsSaving(true);
225
- try {
226
- await onSave(data);
227
- addSuccessMessage('Spreadsheet saved successfully');
228
- } catch (error) {
229
- console.error('Error saving spreadsheet:', error);
230
- addErrorMessage('Failed to save spreadsheet');
231
- } finally {
232
- setIsSaving(false);
233
- }
234
- }, [onSave, data]);
235
-
236
- // Build toolbar
237
- const toolbar = showToolbar && (
238
- <Stack direction="horizontal" spacing="md" align="center" className="mb-4">
239
- {title && <div className="text-lg font-medium text-ink-900 flex-1">{title}</div>}
240
-
241
- {enableImport && (
242
- <label>
243
- <input
244
- type="file"
245
- accept=".xlsx,.xls,.csv"
246
- onChange={handleImport}
247
- className="hidden"
248
- />
249
- <Button variant="ghost" size="sm" icon={<Upload className="h-4 w-4" />}>
250
- Import
251
- </Button>
252
- </label>
253
- )}
254
-
255
- {enableExport && (
256
- <Button
257
- variant="ghost"
258
- size="sm"
259
- icon={<Download className="h-4 w-4" />}
260
- onClick={handleExport}
261
- >
262
- Export
263
- </Button>
264
- )}
265
-
266
- {enableSave && onSave && (
267
- <Button
268
- variant="primary"
269
- size="sm"
270
- icon={<Save className="h-4 w-4" />}
271
- onClick={handleSave}
272
- loading={isSaving}
273
- >
274
- Save
275
- </Button>
276
- )}
277
-
278
- {actions}
279
- </Stack>
280
- );
281
-
282
- // Spreadsheet component
283
- const spreadsheet = (
284
- <div className={`spreadsheet-container ${className}`}>
285
- <BaseSpreadsheet
286
- data={data}
287
- onChange={readOnly ? undefined : handleChange}
288
- columnLabels={columnLabels}
289
- rowLabels={rowLabels}
290
- className="notebook-spreadsheet"
291
- />
292
- </div>
293
- );
294
-
295
- // Wrap in card if requested
296
- if (wrapInCard) {
297
- return (
298
- <Card>
299
- {(showToolbar || title) && (
300
- <CardHeader>
301
- {title && !showToolbar && <CardTitle>{title}</CardTitle>}
302
- {toolbar}
303
- </CardHeader>
304
- )}
305
- <CardContent>{spreadsheet}</CardContent>
306
- </Card>
307
- );
308
- }
309
-
310
- return (
311
- <>
312
- {toolbar}
313
- {spreadsheet}
314
- </>
315
- );
316
- };
317
-
318
- /**
319
- * SpreadsheetReport - Pre-configured spreadsheet for report designer
320
- *
321
- * A ready-to-use spreadsheet component specifically designed for building
322
- * and editing reports with formulas, import/export, and save functionality.
323
- *
324
- * @example
325
- * ```tsx
326
- * const [reportData, setReportData] = useState<Matrix<SpreadsheetCell>>();
327
- *
328
- * <SpreadsheetReport
329
- * data={reportData}
330
- * onChange={setReportData}
331
- * title="Monthly Sales Report"
332
- * onSave={async (data) => {
333
- * await api.saveReport(reportId, data);
334
- * }}
335
- * />
336
- * ```
337
- */
338
- export const SpreadsheetReport: React.FC<
339
- Omit<SpreadsheetProps, 'showToolbar' | 'enableImport' | 'enableExport' | 'enableSave' | 'wrapInCard'>
340
- > = (props) => {
341
- return (
342
- <Spreadsheet
343
- {...props}
344
- showToolbar
345
- enableImport
346
- enableExport
347
- enableSave
348
- wrapInCard
349
- />
350
- );
351
- };
1
+ import React, { useState, useCallback } from 'react';
2
+ import BaseSpreadsheet, { CellBase, Matrix } from 'react-spreadsheet';
3
+ import { read, utils, writeFile, WorkBook } from 'xlsx';
4
+ import Button from './Button';
5
+ import Card, { CardHeader, CardTitle, CardContent } from './Card';
6
+ import Stack from './Stack';
7
+ import { Download, Upload, Save } from 'lucide-react';
8
+ import { addSuccessMessage, addErrorMessage } from './StatusBar';
9
+ import './Spreadsheet.css';
10
+
11
+ // Re-export types for external use
12
+ export type { CellBase, Matrix } from 'react-spreadsheet';
13
+
14
+ /**
15
+ * Enhanced cell type with formula support
16
+ */
17
+ export interface SpreadsheetCell extends CellBase {
18
+ value: string | number | boolean;
19
+ formula?: string;
20
+ readOnly?: boolean;
21
+ className?: string;
22
+ }
23
+
24
+ /**
25
+ * Spreadsheet component props
26
+ */
27
+ export interface SpreadsheetProps {
28
+ /** Initial data matrix */
29
+ data?: Matrix<SpreadsheetCell>;
30
+ /** Callback when data changes */
31
+ onChange?: (data: Matrix<SpreadsheetCell>) => void;
32
+ /** Number of rows to display */
33
+ rows?: number;
34
+ /** Number of columns to display */
35
+ columns?: number;
36
+ /** Column labels (A, B, C... if not provided) */
37
+ columnLabels?: string[];
38
+ /** Row labels (1, 2, 3... if not provided) */
39
+ rowLabels?: string[];
40
+ /** Show toolbar with actions */
41
+ showToolbar?: boolean;
42
+ /** Enable Excel import */
43
+ enableImport?: boolean;
44
+ /** Enable Excel export */
45
+ enableExport?: boolean;
46
+ /** Enable save button */
47
+ enableSave?: boolean;
48
+ /** Save handler */
49
+ onSave?: (data: Matrix<SpreadsheetCell>) => Promise<void> | void;
50
+ /** Title to display in toolbar */
51
+ title?: string;
52
+ /** Additional toolbar actions */
53
+ actions?: React.ReactNode;
54
+ /** Wrap in Card component */
55
+ wrapInCard?: boolean;
56
+ /** Custom className for the spreadsheet container */
57
+ className?: string;
58
+ /** Make entire spreadsheet read-only */
59
+ readOnly?: boolean;
60
+ /** Default export filename */
61
+ exportFileName?: string;
62
+ }
63
+
64
+ /**
65
+ * Spreadsheet - Interactive spreadsheet component with formula support
66
+ *
67
+ * A full-featured spreadsheet component for report designers and data editing.
68
+ * Built on react-spreadsheet with Fast Formula Parser (280+ Excel formulas).
69
+ *
70
+ * **Features:**
71
+ * - Excel-like formula support (SUM, AVERAGE, VLOOKUP, IF, etc.)
72
+ * - Excel import/export with SheetJS
73
+ * - Save/load functionality
74
+ * - Keyboard navigation
75
+ * - Copy/paste support
76
+ * - Customizable styling to match notebook-ui aesthetic
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * // Basic spreadsheet
81
+ * <Spreadsheet
82
+ * rows={10}
83
+ * columns={5}
84
+ * showToolbar
85
+ * />
86
+ *
87
+ * // Report designer with formulas
88
+ * const [reportData, setReportData] = useState<Matrix<SpreadsheetCell>>([
89
+ * [{ value: 'Q1' }, { value: 100 }],
90
+ * [{ value: 'Q2' }, { value: 200 }],
91
+ * [{ value: 'Total' }, { formula: '=SUM(B1:B2)' }],
92
+ * ]);
93
+ *
94
+ * <Spreadsheet
95
+ * data={reportData}
96
+ * onChange={setReportData}
97
+ * title="Sales Report"
98
+ * showToolbar
99
+ * enableImport
100
+ * enableExport
101
+ * enableSave
102
+ * onSave={async (data) => {
103
+ * await saveReport(data);
104
+ * }}
105
+ * />
106
+ *
107
+ * // With custom actions
108
+ * <Spreadsheet
109
+ * data={data}
110
+ * onChange={setData}
111
+ * showToolbar
112
+ * actions={
113
+ * <Button onClick={handleCustomAction}>Custom Action</Button>
114
+ * }
115
+ * />
116
+ * ```
117
+ */
118
+ export const Spreadsheet: React.FC<SpreadsheetProps> = ({
119
+ data: initialData,
120
+ onChange,
121
+ rows = 20,
122
+ columns = 10,
123
+ columnLabels,
124
+ rowLabels,
125
+ showToolbar = false,
126
+ enableImport = false,
127
+ enableExport = false,
128
+ enableSave = false,
129
+ onSave,
130
+ title,
131
+ actions,
132
+ wrapInCard = false,
133
+ className = '',
134
+ readOnly = false,
135
+ exportFileName = 'spreadsheet.xlsx',
136
+ }) => {
137
+ // Initialize data if not provided
138
+ const [data, setData] = useState<Matrix<SpreadsheetCell>>(() => {
139
+ if (initialData) return initialData;
140
+ return Array(rows)
141
+ .fill(null)
142
+ .map(() => Array(columns).fill(null).map(() => ({ value: '' })));
143
+ });
144
+
145
+ const [isSaving, setIsSaving] = useState(false);
146
+
147
+ // Handle data changes
148
+ const handleChange = useCallback(
149
+ (newData: Matrix<SpreadsheetCell>) => {
150
+ setData(newData);
151
+ onChange?.(newData);
152
+ },
153
+ [onChange]
154
+ );
155
+
156
+ // Handle Excel import
157
+ const handleImport = useCallback(
158
+ (event: React.ChangeEvent<HTMLInputElement>) => {
159
+ const file = event.target.files?.[0];
160
+ if (!file) return;
161
+
162
+ const reader = new FileReader();
163
+ reader.onload = (e) => {
164
+ try {
165
+ const workbook: WorkBook = read(e.target?.result, { type: 'binary' });
166
+ const sheetName = workbook.SheetNames[0];
167
+ const worksheet = workbook.Sheets[sheetName];
168
+
169
+ // Convert to array of arrays
170
+ const jsonData: any[][] = utils.sheet_to_json(worksheet, { header: 1 });
171
+
172
+ // Convert to spreadsheet format
173
+ const spreadsheetData: Matrix<SpreadsheetCell> = jsonData.map(row =>
174
+ row.map(cell => ({
175
+ value: cell,
176
+ }))
177
+ );
178
+
179
+ handleChange(spreadsheetData);
180
+ addSuccessMessage('Excel file imported successfully');
181
+ } catch (error) {
182
+ console.error('Error importing Excel file:', error);
183
+ addErrorMessage('Failed to import Excel file');
184
+ }
185
+ };
186
+ reader.readAsBinaryString(file);
187
+
188
+ // Reset input
189
+ event.target.value = '';
190
+ },
191
+ [handleChange]
192
+ );
193
+
194
+ // Handle Excel export
195
+ const handleExport = useCallback(() => {
196
+ try {
197
+ // Convert spreadsheet data to worksheet format
198
+ const worksheetData = data.map(row =>
199
+ row.map(cell => {
200
+ // If cell has a formula, export the calculated value
201
+ if (cell?.formula) {
202
+ return cell.value ?? cell.formula;
203
+ }
204
+ return cell?.value ?? '';
205
+ })
206
+ );
207
+
208
+ const worksheet = utils.aoa_to_sheet(worksheetData);
209
+ const workbook = utils.book_new();
210
+ utils.book_append_sheet(workbook, worksheet, 'Sheet1');
211
+
212
+ writeFile(workbook, exportFileName);
213
+ addSuccessMessage('Excel file exported successfully');
214
+ } catch (error) {
215
+ console.error('Error exporting Excel file:', error);
216
+ addErrorMessage('Failed to export Excel file');
217
+ }
218
+ }, [data, exportFileName]);
219
+
220
+ // Handle save
221
+ const handleSave = useCallback(async () => {
222
+ if (!onSave) return;
223
+
224
+ setIsSaving(true);
225
+ try {
226
+ await onSave(data);
227
+ addSuccessMessage('Spreadsheet saved successfully');
228
+ } catch (error) {
229
+ console.error('Error saving spreadsheet:', error);
230
+ addErrorMessage('Failed to save spreadsheet');
231
+ } finally {
232
+ setIsSaving(false);
233
+ }
234
+ }, [onSave, data]);
235
+
236
+ // Build toolbar
237
+ const toolbar = showToolbar && (
238
+ <Stack direction="horizontal" spacing="md" align="center" className="mb-4">
239
+ {title && <div className="text-lg font-medium text-ink-900 flex-1">{title}</div>}
240
+
241
+ {enableImport && (
242
+ <label>
243
+ <input
244
+ type="file"
245
+ accept=".xlsx,.xls,.csv"
246
+ onChange={handleImport}
247
+ className="hidden"
248
+ />
249
+ <Button variant="ghost" size="sm" icon={<Upload className="h-4 w-4" />}>
250
+ Import
251
+ </Button>
252
+ </label>
253
+ )}
254
+
255
+ {enableExport && (
256
+ <Button
257
+ variant="ghost"
258
+ size="sm"
259
+ icon={<Download className="h-4 w-4" />}
260
+ onClick={handleExport}
261
+ >
262
+ Export
263
+ </Button>
264
+ )}
265
+
266
+ {enableSave && onSave && (
267
+ <Button
268
+ variant="primary"
269
+ size="sm"
270
+ icon={<Save className="h-4 w-4" />}
271
+ onClick={handleSave}
272
+ loading={isSaving}
273
+ >
274
+ Save
275
+ </Button>
276
+ )}
277
+
278
+ {actions}
279
+ </Stack>
280
+ );
281
+
282
+ // Spreadsheet component
283
+ const spreadsheet = (
284
+ <div className={`spreadsheet-container ${className}`}>
285
+ <BaseSpreadsheet
286
+ data={data}
287
+ onChange={readOnly ? undefined : handleChange}
288
+ columnLabels={columnLabels}
289
+ rowLabels={rowLabels}
290
+ className="notebook-spreadsheet"
291
+ />
292
+ </div>
293
+ );
294
+
295
+ // Wrap in card if requested
296
+ if (wrapInCard) {
297
+ return (
298
+ <Card>
299
+ {(showToolbar || title) && (
300
+ <CardHeader>
301
+ {title && !showToolbar && <CardTitle>{title}</CardTitle>}
302
+ {toolbar}
303
+ </CardHeader>
304
+ )}
305
+ <CardContent>{spreadsheet}</CardContent>
306
+ </Card>
307
+ );
308
+ }
309
+
310
+ return (
311
+ <>
312
+ {toolbar}
313
+ {spreadsheet}
314
+ </>
315
+ );
316
+ };
317
+
318
+ /**
319
+ * SpreadsheetReport - Pre-configured spreadsheet for report designer
320
+ *
321
+ * A ready-to-use spreadsheet component specifically designed for building
322
+ * and editing reports with formulas, import/export, and save functionality.
323
+ *
324
+ * @example
325
+ * ```tsx
326
+ * const [reportData, setReportData] = useState<Matrix<SpreadsheetCell>>();
327
+ *
328
+ * <SpreadsheetReport
329
+ * data={reportData}
330
+ * onChange={setReportData}
331
+ * title="Monthly Sales Report"
332
+ * onSave={async (data) => {
333
+ * await api.saveReport(reportId, data);
334
+ * }}
335
+ * />
336
+ * ```
337
+ */
338
+ export const SpreadsheetReport: React.FC<
339
+ Omit<SpreadsheetProps, 'showToolbar' | 'enableImport' | 'enableExport' | 'enableSave' | 'wrapInCard'>
340
+ > = (props) => {
341
+ return (
342
+ <Spreadsheet
343
+ {...props}
344
+ showToolbar
345
+ enableImport
346
+ enableExport
347
+ enableSave
348
+ wrapInCard
349
+ />
350
+ );
351
+ };