@papernote/ui 1.6.0 → 1.7.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/dist/styles.css CHANGED
@@ -2122,6 +2122,10 @@ input:checked + .slider:before{
2122
2122
  min-width: 8rem;
2123
2123
  }
2124
2124
 
2125
+ .min-w-48{
2126
+ min-width: 12rem;
2127
+ }
2128
+
2125
2129
  .min-w-5{
2126
2130
  min-width: 1.25rem;
2127
2131
  }
@@ -2246,6 +2250,14 @@ input:checked + .slider:before{
2246
2250
  border-collapse: collapse;
2247
2251
  }
2248
2252
 
2253
+ .origin-bottom{
2254
+ transform-origin: bottom;
2255
+ }
2256
+
2257
+ .origin-top{
2258
+ transform-origin: top;
2259
+ }
2260
+
2249
2261
  .-translate-x-1\/2{
2250
2262
  --tw-translate-x: -50%;
2251
2263
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -3198,11 +3210,21 @@ input:checked + .slider:before{
3198
3210
  border-color: rgb(226 232 240 / 0.8);
3199
3211
  }
3200
3212
 
3213
+ .border-stone-100{
3214
+ --tw-border-opacity: 1;
3215
+ border-color: rgb(245 245 244 / var(--tw-border-opacity, 1));
3216
+ }
3217
+
3201
3218
  .border-stone-200{
3202
3219
  --tw-border-opacity: 1;
3203
3220
  border-color: rgb(231 229 228 / var(--tw-border-opacity, 1));
3204
3221
  }
3205
3222
 
3223
+ .border-stone-50{
3224
+ --tw-border-opacity: 1;
3225
+ border-color: rgb(250 250 249 / var(--tw-border-opacity, 1));
3226
+ }
3227
+
3206
3228
  .border-success-200{
3207
3229
  --tw-border-opacity: 1;
3208
3230
  border-color: rgb(187 247 208 / var(--tw-border-opacity, 1));
@@ -3480,6 +3502,16 @@ input:checked + .slider:before{
3480
3502
  background-color: rgb(248 250 252 / 0.8);
3481
3503
  }
3482
3504
 
3505
+ .bg-stone-100{
3506
+ --tw-bg-opacity: 1;
3507
+ background-color: rgb(245 245 244 / var(--tw-bg-opacity, 1));
3508
+ }
3509
+
3510
+ .bg-stone-50{
3511
+ --tw-bg-opacity: 1;
3512
+ background-color: rgb(250 250 249 / var(--tw-bg-opacity, 1));
3513
+ }
3514
+
3483
3515
  .bg-success-100{
3484
3516
  --tw-bg-opacity: 1;
3485
3517
  background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
@@ -4470,6 +4502,10 @@ input:checked + .slider:before{
4470
4502
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
4471
4503
  }
4472
4504
 
4505
+ .ring-inset{
4506
+ --tw-ring-inset: inset;
4507
+ }
4508
+
4473
4509
  .ring-accent-300{
4474
4510
  --tw-ring-opacity: 1;
4475
4511
  --tw-ring-color: rgb(212 210 200 / var(--tw-ring-opacity, 1));
@@ -4505,6 +4541,11 @@ input:checked + .slider:before{
4505
4541
  --tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity, 1));
4506
4542
  }
4507
4543
 
4544
+ .ring-primary-500{
4545
+ --tw-ring-opacity: 1;
4546
+ --tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity, 1));
4547
+ }
4548
+
4508
4549
  .ring-success-300{
4509
4550
  --tw-ring-opacity: 1;
4510
4551
  --tw-ring-color: rgb(134 239 172 / var(--tw-ring-opacity, 1));
@@ -5119,6 +5160,16 @@ input:checked + .slider:before{
5119
5160
  background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
5120
5161
  }
5121
5162
 
5163
+ .hover\:bg-stone-100:hover{
5164
+ --tw-bg-opacity: 1;
5165
+ background-color: rgb(245 245 244 / var(--tw-bg-opacity, 1));
5166
+ }
5167
+
5168
+ .hover\:bg-stone-200:hover{
5169
+ --tw-bg-opacity: 1;
5170
+ background-color: rgb(231 229 228 / var(--tw-bg-opacity, 1));
5171
+ }
5172
+
5122
5173
  .hover\:bg-success-200:hover{
5123
5174
  --tw-bg-opacity: 1;
5124
5175
  background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1));
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Formula definitions for DataGrid intellisense
3
+ * Based on fast-formula-parser supported functions
4
+ */
5
+ export interface FormulaParameter {
6
+ name: string;
7
+ description: string;
8
+ optional?: boolean;
9
+ }
10
+ export interface FormulaDefinition {
11
+ name: string;
12
+ category: FormulaCategory;
13
+ description: string;
14
+ syntax: string;
15
+ parameters: FormulaParameter[];
16
+ example?: string;
17
+ }
18
+ export type FormulaCategory = 'Math' | 'Statistical' | 'Lookup' | 'Text' | 'Logical' | 'Date' | 'Information' | 'Financial';
19
+ export declare const FORMULA_DEFINITIONS: FormulaDefinition[];
20
+ export declare const FORMULA_NAMES: string[];
21
+ export declare const getFormulasByCategory: (category: FormulaCategory) => FormulaDefinition[];
22
+ export declare const searchFormulas: (query: string) => FormulaDefinition[];
23
+ export declare const getFormula: (name: string) => FormulaDefinition | undefined;
24
+ export declare const FORMULA_CATEGORIES: FormulaCategory[];
25
+ //# sourceMappingURL=formulaDefinitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formulaDefinitions.d.ts","sourceRoot":"","sources":["../../src/utils/formulaDefinitions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,aAAa,GACb,QAAQ,GACR,MAAM,GACN,SAAS,GACT,MAAM,GACN,aAAa,GACb,WAAW,CAAC;AAEhB,eAAO,MAAM,mBAAmB,EAAE,iBAAiB,EAgpClD,CAAC;AAGF,eAAO,MAAM,aAAa,UAAyC,CAAC;AAGpE,eAAO,MAAM,qBAAqB,GAAI,UAAU,eAAe,KAAG,iBAAiB,EACvB,CAAC;AAG7D,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,iBAAiB,EAG/D,CAAC;AAGF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,iBAAiB,GAAG,SACE,CAAC;AAGjE,eAAO,MAAM,kBAAkB,EAAE,eAAe,EAS/C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papernote/ui",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "description": "A modern React component library with a paper notebook aesthetic - minimal, professional, and expressive",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,356 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { DataGrid, DataGridCell, DataGridColumn } from './DataGrid';
3
+
4
+ const meta: Meta<typeof DataGrid> = {
5
+ title: 'Components/DataGrid',
6
+ component: DataGrid,
7
+ parameters: {
8
+ layout: 'padded',
9
+ docs: {
10
+ description: {
11
+ component: `
12
+ DataGrid is an Excel-like spreadsheet component with formula support and intellisense.
13
+
14
+ ## Features
15
+ - **280+ Excel formulas** via fast-formula-parser (MIT licensed)
16
+ - **Formula Intellisense** - Autocomplete with function signatures and parameter hints
17
+ - **Sorting & Filtering** - Click column headers
18
+ - **Frozen rows/columns** - Keep headers visible while scrolling
19
+ - **Cell editing** - Double-click to edit
20
+ - **Keyboard navigation** - Arrow keys, Enter, Escape, Tab
21
+ - **Zebra striping** - Alternating row colors
22
+ - **CSV Export** - Export data to CSV
23
+
24
+ ## Formula Intellisense
25
+ When editing a cell with \`formulas={true}\`:
26
+ 1. Type \`=\` to see all available formulas
27
+ 2. Start typing a function name to filter (e.g., \`=SUM\`, \`=VL\`)
28
+ 3. Use category tabs to browse (Math, Lookup, Text, Logical, Date, etc.)
29
+ 4. Arrow keys navigate, Tab/Enter inserts the function
30
+ 5. After inserting, see parameter hints showing what each argument means
31
+
32
+ ## Supported Formulas
33
+ **Math:** SUM, AVERAGE, MIN, MAX, COUNT, ROUND, ABS, SQRT, PRODUCT, MOD...
34
+ **Lookup:** VLOOKUP, HLOOKUP, INDEX, MATCH, CHOOSE, OFFSET, INDIRECT...
35
+ **Text:** CONCATENATE, LEFT, RIGHT, MID, LEN, UPPER, LOWER, TRIM...
36
+ **Logical:** IF, IFS, AND, OR, NOT, IFERROR, SWITCH...
37
+ **Date:** TODAY, NOW, DATE, YEAR, MONTH, DAY, DATEDIF, NETWORKDAYS...
38
+ **Statistical:** COUNTIF, SUMIF, AVERAGEIF, MEDIAN, STDEV, RANK...
39
+ **Financial:** PMT, PV, FV, NPV, IRR, RATE...
40
+
41
+ ## Frozen Rows
42
+ - \`'none'\` - No frozen rows
43
+ - \`'first'\` - Freeze first row (common for headers)
44
+ - \`'selected'\` - Freeze the currently selected row
45
+ - \`number\` - Freeze specific number of rows
46
+ `,
47
+ },
48
+ },
49
+ },
50
+ tags: ['autodocs'],
51
+ };
52
+
53
+ export default meta;
54
+ type Story = StoryObj<typeof DataGrid>;
55
+
56
+ // Sample data
57
+ const basicColumns: DataGridColumn[] = [
58
+ { key: 'name', header: 'Name', width: 150, sortable: true, filterable: true },
59
+ { key: 'age', header: 'Age', width: 80, type: 'number', sortable: true, align: 'right' },
60
+ { key: 'city', header: 'City', width: 150, sortable: true, filterable: true },
61
+ { key: 'country', header: 'Country', width: 120, sortable: true },
62
+ ];
63
+
64
+ const basicData: DataGridCell[][] = [
65
+ [{ value: 'John Smith' }, { value: 32 }, { value: 'New York' }, { value: 'USA' }],
66
+ [{ value: 'Jane Doe' }, { value: 28 }, { value: 'Los Angeles' }, { value: 'USA' }],
67
+ [{ value: 'Bob Johnson' }, { value: 45 }, { value: 'Chicago' }, { value: 'USA' }],
68
+ [{ value: 'Alice Brown' }, { value: 36 }, { value: 'Houston' }, { value: 'USA' }],
69
+ [{ value: 'Charlie Wilson' }, { value: 29 }, { value: 'Phoenix' }, { value: 'USA' }],
70
+ [{ value: 'Diana Miller' }, { value: 41 }, { value: 'Philadelphia' }, { value: 'USA' }],
71
+ [{ value: 'Edward Davis' }, { value: 33 }, { value: 'San Antonio' }, { value: 'USA' }],
72
+ [{ value: 'Fiona Garcia' }, { value: 27 }, { value: 'San Diego' }, { value: 'USA' }],
73
+ ];
74
+
75
+ const salesColumns: DataGridColumn[] = [
76
+ { key: 'product', header: 'Product', width: 150 },
77
+ { key: 'q1', header: 'Q1', width: 100, type: 'number', align: 'right' },
78
+ { key: 'q2', header: 'Q2', width: 100, type: 'number', align: 'right' },
79
+ { key: 'q3', header: 'Q3', width: 100, type: 'number', align: 'right' },
80
+ { key: 'q4', header: 'Q4', width: 100, type: 'number', align: 'right' },
81
+ { key: 'total', header: 'Total', width: 120, type: 'number', align: 'right' },
82
+ ];
83
+
84
+ const salesData: DataGridCell[][] = [
85
+ [
86
+ { value: 'Widget A' },
87
+ { value: 1200 },
88
+ { value: 1350 },
89
+ { value: 1100 },
90
+ { value: 1500 },
91
+ { value: 0, formula: '=SUM(B1:E1)' },
92
+ ],
93
+ [
94
+ { value: 'Widget B' },
95
+ { value: 800 },
96
+ { value: 950 },
97
+ { value: 1200 },
98
+ { value: 1100 },
99
+ { value: 0, formula: '=SUM(B2:E2)' },
100
+ ],
101
+ [
102
+ { value: 'Widget C' },
103
+ { value: 2100 },
104
+ { value: 1800 },
105
+ { value: 2200 },
106
+ { value: 2400 },
107
+ { value: 0, formula: '=SUM(B3:E3)' },
108
+ ],
109
+ [
110
+ { value: 'Widget D' },
111
+ { value: 650 },
112
+ { value: 720 },
113
+ { value: 680 },
114
+ { value: 750 },
115
+ { value: 0, formula: '=SUM(B4:E4)' },
116
+ ],
117
+ [
118
+ { value: 'Total', readOnly: true },
119
+ { value: 0, formula: '=SUM(B1:B4)' },
120
+ { value: 0, formula: '=SUM(C1:C4)' },
121
+ { value: 0, formula: '=SUM(D1:D4)' },
122
+ { value: 0, formula: '=SUM(E1:E4)' },
123
+ { value: 0, formula: '=SUM(F1:F4)' },
124
+ ],
125
+ ];
126
+
127
+ const flightLogColumns: DataGridColumn[] = [
128
+ { key: 'date', header: 'Date', width: 100, sortable: true },
129
+ { key: 'aircraft', header: 'Aircraft', width: 120, sortable: true, filterable: true },
130
+ { key: 'registration', header: 'Registration', width: 100 },
131
+ { key: 'departure', header: 'Departure', width: 90 },
132
+ { key: 'arrival', header: 'Arrival', width: 90 },
133
+ { key: 'flightTime', header: 'Flight Time', width: 100, type: 'number', align: 'right' },
134
+ { key: 'passengers', header: 'Passengers', width: 100, type: 'number', align: 'right' },
135
+ { key: 'pilot', header: 'Pilot', width: 120, sortable: true, filterable: true },
136
+ ];
137
+
138
+ const flightLogData: DataGridCell[][] = [
139
+ [{ value: '2024-01-15' }, { value: 'Cessna 172' }, { value: 'N12345' }, { value: 'KJFK' }, { value: 'KBOS' }, { value: 2.5 }, { value: 3 }, { value: 'John Smith' }],
140
+ [{ value: '2024-01-16' }, { value: 'Piper PA-28' }, { value: 'N67890' }, { value: 'KBOS' }, { value: 'KPHL' }, { value: 1.8 }, { value: 2 }, { value: 'Jane Doe' }],
141
+ [{ value: '2024-01-17' }, { value: 'Cessna 182' }, { value: 'N11111' }, { value: 'KPHL' }, { value: 'KDCA' }, { value: 1.2 }, { value: 4 }, { value: 'Bob Johnson' }],
142
+ [{ value: '2024-01-18' }, { value: 'Beechcraft Baron' }, { value: 'N22222' }, { value: 'KDCA' }, { value: 'KATL' }, { value: 2.1 }, { value: 5 }, { value: 'Alice Brown' }],
143
+ [{ value: '2024-01-19' }, { value: 'Cessna 172' }, { value: 'N12345' }, { value: 'KATL' }, { value: 'KMIA' }, { value: 2.8 }, { value: 3 }, { value: 'John Smith' }],
144
+ [{ value: '2024-01-20' }, { value: 'Piper PA-28' }, { value: 'N67890' }, { value: 'KMIA' }, { value: 'KTPA' }, { value: 0.9 }, { value: 2 }, { value: 'Charlie Wilson' }],
145
+ [{ value: '2024-01-21' }, { value: 'Cessna 182' }, { value: 'N11111' }, { value: 'KTPA' }, { value: 'KJAX' }, { value: 1.1 }, { value: 4 }, { value: 'Diana Miller' }],
146
+ [{ value: '2024-01-22' }, { value: 'Beechcraft Baron' }, { value: 'N22222' }, { value: 'KJAX' }, { value: 'KCLT' }, { value: 1.7 }, { value: 5 }, { value: 'Edward Davis' }],
147
+ [{ value: '2024-01-23' }, { value: 'Cessna 172' }, { value: 'N12345' }, { value: 'KCLT' }, { value: 'KRDU' }, { value: 0.8 }, { value: 3 }, { value: 'Fiona Garcia' }],
148
+ [{ value: '2024-01-24' }, { value: 'Piper PA-28' }, { value: 'N67890' }, { value: 'KRDU' }, { value: 'KRIC' }, { value: 0.6 }, { value: 2 }, { value: 'John Smith' }],
149
+ ];
150
+
151
+ /**
152
+ * Basic grid with sorting and filtering
153
+ */
154
+ export const Basic: Story = {
155
+ args: {
156
+ data: basicData,
157
+ columns: basicColumns,
158
+ rowHeaders: true,
159
+ height: 350,
160
+ },
161
+ };
162
+
163
+ /**
164
+ * With Excel formulas - demonstrates SUM and cell references
165
+ *
166
+ * **Formula Intellisense:**
167
+ * - Double-click any cell and type `=` to see formula autocomplete
168
+ * - Use arrow keys to navigate, Tab/Enter to insert
169
+ * - Browse by category (Math, Lookup, Text, etc.)
170
+ * - See parameter hints as you type
171
+ *
172
+ * Try typing `=SUM(`, `=VLOOKUP(`, `=IF(` etc.
173
+ */
174
+ export const WithFormulas: Story = {
175
+ args: {
176
+ data: salesData,
177
+ columns: salesColumns,
178
+ formulas: true,
179
+ rowHeaders: true,
180
+ height: 300,
181
+ showToolbar: true,
182
+ title: 'Quarterly Sales Report',
183
+ },
184
+ };
185
+
186
+ /**
187
+ * Frozen first row - header stays visible while scrolling
188
+ */
189
+ export const FrozenFirstRow: Story = {
190
+ args: {
191
+ data: flightLogData,
192
+ columns: flightLogColumns,
193
+ frozenRows: 'first',
194
+ rowHeaders: true,
195
+ zebraStripes: true,
196
+ height: 300,
197
+ showToolbar: true,
198
+ title: 'Flight Log',
199
+ showFreezeRowToggle: true,
200
+ },
201
+ };
202
+
203
+ /**
204
+ * Frozen columns - first column stays visible while scrolling horizontally
205
+ */
206
+ export const FrozenColumns: Story = {
207
+ args: {
208
+ data: flightLogData,
209
+ columns: flightLogColumns,
210
+ frozenColumns: 2,
211
+ rowHeaders: true,
212
+ zebraStripes: true,
213
+ height: 300,
214
+ },
215
+ };
216
+
217
+ /**
218
+ * Read-only data viewer with zebra stripes
219
+ */
220
+ export const ReadOnlyViewer: Story = {
221
+ args: {
222
+ data: flightLogData,
223
+ columns: flightLogColumns,
224
+ readOnly: true,
225
+ zebraStripes: true,
226
+ rowHeaders: true,
227
+ height: 350,
228
+ showToolbar: true,
229
+ title: 'Flight Log Viewer',
230
+ enableExport: true,
231
+ exportFileName: 'flight-log.csv',
232
+ },
233
+ };
234
+
235
+ /**
236
+ * With toolbar - export and save functionality
237
+ */
238
+ export const WithToolbar: Story = {
239
+ args: {
240
+ data: salesData,
241
+ columns: salesColumns,
242
+ formulas: true,
243
+ showToolbar: true,
244
+ title: 'Sales Dashboard',
245
+ enableExport: true,
246
+ enableSave: true,
247
+ showFreezeRowToggle: true,
248
+ onSave: async (data) => {
249
+ console.log('Saving data:', data);
250
+ await new Promise((resolve) => setTimeout(resolve, 1000));
251
+ },
252
+ height: 250,
253
+ },
254
+ };
255
+
256
+ /**
257
+ * Compact density - more data in less space
258
+ */
259
+ export const CompactDensity: Story = {
260
+ args: {
261
+ data: flightLogData,
262
+ columns: flightLogColumns,
263
+ density: 'compact',
264
+ zebraStripes: true,
265
+ rowHeaders: true,
266
+ height: 300,
267
+ },
268
+ };
269
+
270
+ /**
271
+ * Comfortable density - more space between rows
272
+ */
273
+ export const ComfortableDensity: Story = {
274
+ args: {
275
+ data: basicData,
276
+ columns: basicColumns,
277
+ density: 'comfortable',
278
+ zebraStripes: true,
279
+ rowHeaders: true,
280
+ height: 400,
281
+ },
282
+ };
283
+
284
+ /**
285
+ * Editable grid - double-click to edit cells
286
+ */
287
+ export const Editable: Story = {
288
+ args: {
289
+ data: [
290
+ [{ value: 'Task 1' }, { value: 'In Progress' }, { value: 'High' }, { value: '2024-02-01' }],
291
+ [{ value: 'Task 2' }, { value: 'Done' }, { value: 'Medium' }, { value: '2024-01-28' }],
292
+ [{ value: 'Task 3' }, { value: 'Pending' }, { value: 'Low' }, { value: '2024-02-05' }],
293
+ [{ value: '' }, { value: '' }, { value: '' }, { value: '' }],
294
+ [{ value: '' }, { value: '' }, { value: '' }, { value: '' }],
295
+ ],
296
+ columns: [
297
+ { key: 'task', header: 'Task', width: 200 },
298
+ { key: 'status', header: 'Status', width: 120 },
299
+ { key: 'priority', header: 'Priority', width: 100 },
300
+ { key: 'dueDate', header: 'Due Date', width: 120 },
301
+ ],
302
+ rowHeaders: true,
303
+ height: 250,
304
+ onChange: (data, row, col) => {
305
+ console.log(`Cell changed at row ${row}, col ${col}:`, data[row][col]);
306
+ },
307
+ },
308
+ };
309
+
310
+ /**
311
+ * Full featured example - all features enabled
312
+ */
313
+ export const FullFeatured: Story = {
314
+ args: {
315
+ data: salesData,
316
+ columns: salesColumns,
317
+ formulas: true,
318
+ frozenRows: 'first',
319
+ frozenColumns: 1,
320
+ zebraStripes: true,
321
+ rowHeaders: true,
322
+ showToolbar: true,
323
+ title: 'Full Featured DataGrid',
324
+ enableExport: true,
325
+ enableSave: true,
326
+ showFreezeRowToggle: true,
327
+ onSave: async (data) => {
328
+ console.log('Saving:', data);
329
+ await new Promise((resolve) => setTimeout(resolve, 1000));
330
+ },
331
+ height: 300,
332
+ },
333
+ };
334
+
335
+ /**
336
+ * Currency formatting example
337
+ */
338
+ export const CurrencyFormatting: Story = {
339
+ args: {
340
+ data: [
341
+ [{ value: 'Product A' }, { value: 1234.56 }, { value: 100 }, { value: 0, formula: '=B1*C1' }],
342
+ [{ value: 'Product B' }, { value: 789.99 }, { value: 50 }, { value: 0, formula: '=B2*C2' }],
343
+ [{ value: 'Product C' }, { value: 2345.00 }, { value: 75 }, { value: 0, formula: '=B3*C3' }],
344
+ [{ value: 'Total' }, { value: 0, formula: '=SUM(B1:B3)' }, { value: 0, formula: '=SUM(C1:C3)' }, { value: 0, formula: '=SUM(D1:D3)' }],
345
+ ],
346
+ columns: [
347
+ { key: 'product', header: 'Product', width: 150 },
348
+ { key: 'price', header: 'Price', width: 120, type: 'currency', align: 'right' },
349
+ { key: 'qty', header: 'Quantity', width: 100, type: 'number', align: 'right' },
350
+ { key: 'total', header: 'Total', width: 150, type: 'currency', align: 'right' },
351
+ ],
352
+ formulas: true,
353
+ rowHeaders: true,
354
+ height: 220,
355
+ },
356
+ };