@papernote/ui 1.10.19 → 1.10.21
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/components/Alert.d.ts +1 -1
- package/dist/components/Alert.d.ts.map +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.d.ts.map +1 -1
- package/dist/components/PivotTable.d.ts +47 -0
- package/dist/components/PivotTable.d.ts.map +1 -0
- package/dist/components/StatusBadge.d.ts +1 -1
- package/dist/components/StatusBadge.d.ts.map +1 -1
- package/dist/components/Text.d.ts +2 -1
- package/dist/components/Text.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +53 -6
- package/dist/index.esm.js +271 -37
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +271 -36
- package/dist/index.js.map +1 -1
- package/dist/styles.css +15 -0
- package/package.json +1 -1
- package/src/components/Alert.stories.tsx +20 -7
- package/src/components/Alert.tsx +7 -1
- package/src/components/Badge.stories.tsx +18 -7
- package/src/components/Badge.tsx +3 -1
- package/src/components/PivotTable.stories.tsx +188 -0
- package/src/components/PivotTable.tsx +422 -0
- package/src/components/StatusBadge.tsx +7 -2
- package/src/components/Text.stories.tsx +30 -6
- package/src/components/Text.tsx +5 -3
- package/src/components/index.ts +4 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PivotTable - Transforms flat data into a cross-tabulation table
|
|
5
|
+
*
|
|
6
|
+
* Takes data like:
|
|
7
|
+
* [{ principal: 'A', month: 1, amount: 100 }, { principal: 'A', month: 2, amount: 200 }, ...]
|
|
8
|
+
*
|
|
9
|
+
* And displays it as:
|
|
10
|
+
* | Principal | January | February | ... | Total | Average |
|
|
11
|
+
* |-----------|---------|----------|-----|-------|---------|
|
|
12
|
+
* | A | $100 | $200 | ... | $300 | $150 |
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface PivotTableProps<T = Record<string, unknown>> {
|
|
16
|
+
/** Array of data rows to pivot */
|
|
17
|
+
data: T[];
|
|
18
|
+
/** Field name to use for row labels */
|
|
19
|
+
rowField: string;
|
|
20
|
+
/** Field name to use for column headers */
|
|
21
|
+
columnField: string;
|
|
22
|
+
/** Field name containing the values to display */
|
|
23
|
+
valueField: string;
|
|
24
|
+
/** Optional map of column field values to display labels */
|
|
25
|
+
columnLabels?: Record<string | number, string>;
|
|
26
|
+
/** Optional row label header text */
|
|
27
|
+
rowLabel?: string;
|
|
28
|
+
/** Format function for values */
|
|
29
|
+
formatValue?: (value: number | null) => string;
|
|
30
|
+
/** Show row totals column */
|
|
31
|
+
showRowTotals?: boolean;
|
|
32
|
+
/** Show row averages column */
|
|
33
|
+
showRowAverages?: boolean;
|
|
34
|
+
/** Show column totals row */
|
|
35
|
+
showColumnTotals?: boolean;
|
|
36
|
+
/** Custom className */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** Sort columns by label or value */
|
|
39
|
+
sortColumns?: 'label' | 'value' | 'none';
|
|
40
|
+
/** Sort rows alphabetically */
|
|
41
|
+
sortRows?: boolean;
|
|
42
|
+
/** Empty cell placeholder */
|
|
43
|
+
emptyValue?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PivotedData {
|
|
47
|
+
rowLabels: string[];
|
|
48
|
+
columnLabels: string[];
|
|
49
|
+
columnKeys: (string | number)[];
|
|
50
|
+
matrix: (number | null)[][];
|
|
51
|
+
rowTotals: number[];
|
|
52
|
+
rowAverages: number[];
|
|
53
|
+
columnTotals: number[];
|
|
54
|
+
grandTotal: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Default currency formatter
|
|
59
|
+
*/
|
|
60
|
+
const defaultFormatValue = (value: number | null): string => {
|
|
61
|
+
if (value === null || value === undefined) return '-';
|
|
62
|
+
return new Intl.NumberFormat('en-US', {
|
|
63
|
+
style: 'currency',
|
|
64
|
+
currency: 'USD',
|
|
65
|
+
minimumFractionDigits: 2,
|
|
66
|
+
maximumFractionDigits: 2,
|
|
67
|
+
}).format(value);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Transform flat data into pivoted structure
|
|
72
|
+
*/
|
|
73
|
+
function pivotData<T>(
|
|
74
|
+
data: T[],
|
|
75
|
+
rowField: string,
|
|
76
|
+
columnField: string,
|
|
77
|
+
valueField: string,
|
|
78
|
+
columnLabels?: Record<string | number, string>,
|
|
79
|
+
sortColumns?: 'label' | 'value' | 'none',
|
|
80
|
+
sortRows?: boolean
|
|
81
|
+
): PivotedData {
|
|
82
|
+
// Extract unique row and column values
|
|
83
|
+
const rowSet = new Set<string>();
|
|
84
|
+
const columnSet = new Set<string | number>();
|
|
85
|
+
const valueMap = new Map<string, number>();
|
|
86
|
+
|
|
87
|
+
for (const row of data) {
|
|
88
|
+
const rowValue = String((row as Record<string, unknown>)[rowField] ?? '');
|
|
89
|
+
const colValue = (row as Record<string, unknown>)[columnField];
|
|
90
|
+
const val = (row as Record<string, unknown>)[valueField];
|
|
91
|
+
|
|
92
|
+
if (rowValue) rowSet.add(rowValue);
|
|
93
|
+
if (colValue !== undefined && colValue !== null) columnSet.add(colValue as string | number);
|
|
94
|
+
|
|
95
|
+
// Create composite key for the cell
|
|
96
|
+
const key = `${rowValue}|||${colValue}`;
|
|
97
|
+
const numVal = typeof val === 'number' ? val : parseFloat(String(val)) || 0;
|
|
98
|
+
valueMap.set(key, (valueMap.get(key) || 0) + numVal);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Sort row labels
|
|
102
|
+
let rowLabels = Array.from(rowSet);
|
|
103
|
+
if (sortRows !== false) {
|
|
104
|
+
rowLabels.sort((a, b) => a.localeCompare(b));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Sort column keys
|
|
108
|
+
let columnKeys = Array.from(columnSet);
|
|
109
|
+
if (sortColumns === 'label') {
|
|
110
|
+
columnKeys.sort((a, b) => {
|
|
111
|
+
const labelA = columnLabels?.[a] ?? String(a);
|
|
112
|
+
const labelB = columnLabels?.[b] ?? String(b);
|
|
113
|
+
return labelA.localeCompare(labelB);
|
|
114
|
+
});
|
|
115
|
+
} else if (sortColumns !== 'none') {
|
|
116
|
+
// Default: sort by value (numeric if possible)
|
|
117
|
+
columnKeys.sort((a, b) => {
|
|
118
|
+
const numA = typeof a === 'number' ? a : parseFloat(String(a));
|
|
119
|
+
const numB = typeof b === 'number' ? b : parseFloat(String(b));
|
|
120
|
+
if (!isNaN(numA) && !isNaN(numB)) return numA - numB;
|
|
121
|
+
return String(a).localeCompare(String(b));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Build column labels array
|
|
126
|
+
const colLabels = columnKeys.map((k) => columnLabels?.[k] ?? String(k));
|
|
127
|
+
|
|
128
|
+
// Build the matrix
|
|
129
|
+
const matrix: (number | null)[][] = [];
|
|
130
|
+
const rowTotals: number[] = [];
|
|
131
|
+
const rowAverages: number[] = [];
|
|
132
|
+
const columnTotals: number[] = new Array(columnKeys.length).fill(0);
|
|
133
|
+
let grandTotal = 0;
|
|
134
|
+
|
|
135
|
+
for (const rowLabel of rowLabels) {
|
|
136
|
+
const rowData: (number | null)[] = [];
|
|
137
|
+
let rowSum = 0;
|
|
138
|
+
let rowCount = 0;
|
|
139
|
+
|
|
140
|
+
for (let colIdx = 0; colIdx < columnKeys.length; colIdx++) {
|
|
141
|
+
const colKey = columnKeys[colIdx];
|
|
142
|
+
const key = `${rowLabel}|||${colKey}`;
|
|
143
|
+
const value = valueMap.get(key) ?? null;
|
|
144
|
+
rowData.push(value);
|
|
145
|
+
|
|
146
|
+
if (value !== null) {
|
|
147
|
+
rowSum += value;
|
|
148
|
+
rowCount++;
|
|
149
|
+
columnTotals[colIdx] += value;
|
|
150
|
+
grandTotal += value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
matrix.push(rowData);
|
|
155
|
+
rowTotals.push(rowSum);
|
|
156
|
+
rowAverages.push(rowCount > 0 ? rowSum / rowCount : 0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
rowLabels,
|
|
161
|
+
columnLabels: colLabels,
|
|
162
|
+
columnKeys,
|
|
163
|
+
matrix,
|
|
164
|
+
rowTotals,
|
|
165
|
+
rowAverages,
|
|
166
|
+
columnTotals,
|
|
167
|
+
grandTotal,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* PivotTable Component
|
|
173
|
+
*/
|
|
174
|
+
export function PivotTable<T = Record<string, unknown>>({
|
|
175
|
+
data,
|
|
176
|
+
rowField,
|
|
177
|
+
columnField,
|
|
178
|
+
valueField,
|
|
179
|
+
columnLabels,
|
|
180
|
+
rowLabel = '',
|
|
181
|
+
formatValue = defaultFormatValue,
|
|
182
|
+
showRowTotals = true,
|
|
183
|
+
showRowAverages = false,
|
|
184
|
+
showColumnTotals = true,
|
|
185
|
+
className = '',
|
|
186
|
+
sortColumns = 'value',
|
|
187
|
+
sortRows = true,
|
|
188
|
+
emptyValue = '-',
|
|
189
|
+
}: PivotTableProps<T>) {
|
|
190
|
+
const pivoted = useMemo(
|
|
191
|
+
() => pivotData(data, rowField, columnField, valueField, columnLabels, sortColumns, sortRows),
|
|
192
|
+
[data, rowField, columnField, valueField, columnLabels, sortColumns, sortRows]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const formatCell = (value: number | null): string => {
|
|
196
|
+
if (value === null || value === undefined) return emptyValue;
|
|
197
|
+
return formatValue(value);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Calculate column totals row average
|
|
201
|
+
const columnTotalsAverage =
|
|
202
|
+
pivoted.columnTotals.length > 0
|
|
203
|
+
? pivoted.columnTotals.reduce((a, b) => a + b, 0) / pivoted.columnTotals.length
|
|
204
|
+
: 0;
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<div className={`pivot-table-container ${className}`} style={{ overflowX: 'auto' }}>
|
|
208
|
+
<table
|
|
209
|
+
style={{
|
|
210
|
+
width: '100%',
|
|
211
|
+
borderCollapse: 'collapse',
|
|
212
|
+
fontSize: '14px',
|
|
213
|
+
fontFamily: 'inherit',
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
<thead>
|
|
217
|
+
<tr style={{ backgroundColor: '#f8fafc', borderBottom: '2px solid #e2e8f0' }}>
|
|
218
|
+
{/* Row label header */}
|
|
219
|
+
<th
|
|
220
|
+
style={{
|
|
221
|
+
padding: '12px 16px',
|
|
222
|
+
textAlign: 'left',
|
|
223
|
+
fontWeight: 600,
|
|
224
|
+
color: '#334155',
|
|
225
|
+
position: 'sticky',
|
|
226
|
+
left: 0,
|
|
227
|
+
backgroundColor: '#f8fafc',
|
|
228
|
+
zIndex: 1,
|
|
229
|
+
minWidth: '150px',
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
{rowLabel}
|
|
233
|
+
</th>
|
|
234
|
+
{/* Column headers */}
|
|
235
|
+
{pivoted.columnLabels.map((label, idx) => (
|
|
236
|
+
<th
|
|
237
|
+
key={idx}
|
|
238
|
+
style={{
|
|
239
|
+
padding: '12px 16px',
|
|
240
|
+
textAlign: 'right',
|
|
241
|
+
fontWeight: 600,
|
|
242
|
+
color: '#334155',
|
|
243
|
+
minWidth: '100px',
|
|
244
|
+
whiteSpace: 'nowrap',
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
{label}
|
|
248
|
+
</th>
|
|
249
|
+
))}
|
|
250
|
+
{/* Total header */}
|
|
251
|
+
{showRowTotals && (
|
|
252
|
+
<th
|
|
253
|
+
style={{
|
|
254
|
+
padding: '12px 16px',
|
|
255
|
+
textAlign: 'right',
|
|
256
|
+
fontWeight: 700,
|
|
257
|
+
color: '#1e40af',
|
|
258
|
+
backgroundColor: '#eff6ff',
|
|
259
|
+
minWidth: '100px',
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
Total
|
|
263
|
+
</th>
|
|
264
|
+
)}
|
|
265
|
+
{/* Average header */}
|
|
266
|
+
{showRowAverages && (
|
|
267
|
+
<th
|
|
268
|
+
style={{
|
|
269
|
+
padding: '12px 16px',
|
|
270
|
+
textAlign: 'right',
|
|
271
|
+
fontWeight: 700,
|
|
272
|
+
color: '#166534',
|
|
273
|
+
backgroundColor: '#f0fdf4',
|
|
274
|
+
minWidth: '100px',
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
Average
|
|
278
|
+
</th>
|
|
279
|
+
)}
|
|
280
|
+
</tr>
|
|
281
|
+
</thead>
|
|
282
|
+
<tbody>
|
|
283
|
+
{pivoted.rowLabels.map((rowLabel, rowIdx) => (
|
|
284
|
+
<tr
|
|
285
|
+
key={rowIdx}
|
|
286
|
+
style={{
|
|
287
|
+
borderBottom: '1px solid #e2e8f0',
|
|
288
|
+
backgroundColor: rowIdx % 2 === 0 ? '#ffffff' : '#f8fafc',
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
{/* Row label */}
|
|
292
|
+
<td
|
|
293
|
+
style={{
|
|
294
|
+
padding: '10px 16px',
|
|
295
|
+
fontWeight: 500,
|
|
296
|
+
color: '#1e293b',
|
|
297
|
+
position: 'sticky',
|
|
298
|
+
left: 0,
|
|
299
|
+
backgroundColor: rowIdx % 2 === 0 ? '#ffffff' : '#f8fafc',
|
|
300
|
+
zIndex: 1,
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
303
|
+
{rowLabel}
|
|
304
|
+
</td>
|
|
305
|
+
{/* Data cells */}
|
|
306
|
+
{pivoted.matrix[rowIdx].map((value, colIdx) => (
|
|
307
|
+
<td
|
|
308
|
+
key={colIdx}
|
|
309
|
+
style={{
|
|
310
|
+
padding: '10px 16px',
|
|
311
|
+
textAlign: 'right',
|
|
312
|
+
color: value === null ? '#94a3b8' : '#334155',
|
|
313
|
+
fontVariantNumeric: 'tabular-nums',
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
{formatCell(value)}
|
|
317
|
+
</td>
|
|
318
|
+
))}
|
|
319
|
+
{/* Row total */}
|
|
320
|
+
{showRowTotals && (
|
|
321
|
+
<td
|
|
322
|
+
style={{
|
|
323
|
+
padding: '10px 16px',
|
|
324
|
+
textAlign: 'right',
|
|
325
|
+
fontWeight: 600,
|
|
326
|
+
color: '#1e40af',
|
|
327
|
+
backgroundColor: '#eff6ff',
|
|
328
|
+
fontVariantNumeric: 'tabular-nums',
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
{formatCell(pivoted.rowTotals[rowIdx])}
|
|
332
|
+
</td>
|
|
333
|
+
)}
|
|
334
|
+
{/* Row average */}
|
|
335
|
+
{showRowAverages && (
|
|
336
|
+
<td
|
|
337
|
+
style={{
|
|
338
|
+
padding: '10px 16px',
|
|
339
|
+
textAlign: 'right',
|
|
340
|
+
fontWeight: 600,
|
|
341
|
+
color: '#166534',
|
|
342
|
+
backgroundColor: '#f0fdf4',
|
|
343
|
+
fontVariantNumeric: 'tabular-nums',
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
{formatCell(pivoted.rowAverages[rowIdx])}
|
|
347
|
+
</td>
|
|
348
|
+
)}
|
|
349
|
+
</tr>
|
|
350
|
+
))}
|
|
351
|
+
{/* Column totals row */}
|
|
352
|
+
{showColumnTotals && (
|
|
353
|
+
<tr
|
|
354
|
+
style={{
|
|
355
|
+
borderTop: '2px solid #e2e8f0',
|
|
356
|
+
backgroundColor: '#f1f5f9',
|
|
357
|
+
fontWeight: 600,
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
<td
|
|
361
|
+
style={{
|
|
362
|
+
padding: '12px 16px',
|
|
363
|
+
fontWeight: 700,
|
|
364
|
+
color: '#1e293b',
|
|
365
|
+
position: 'sticky',
|
|
366
|
+
left: 0,
|
|
367
|
+
backgroundColor: '#f1f5f9',
|
|
368
|
+
zIndex: 1,
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
Total
|
|
372
|
+
</td>
|
|
373
|
+
{pivoted.columnTotals.map((total, idx) => (
|
|
374
|
+
<td
|
|
375
|
+
key={idx}
|
|
376
|
+
style={{
|
|
377
|
+
padding: '12px 16px',
|
|
378
|
+
textAlign: 'right',
|
|
379
|
+
color: '#1e293b',
|
|
380
|
+
fontVariantNumeric: 'tabular-nums',
|
|
381
|
+
}}
|
|
382
|
+
>
|
|
383
|
+
{formatCell(total)}
|
|
384
|
+
</td>
|
|
385
|
+
))}
|
|
386
|
+
{showRowTotals && (
|
|
387
|
+
<td
|
|
388
|
+
style={{
|
|
389
|
+
padding: '12px 16px',
|
|
390
|
+
textAlign: 'right',
|
|
391
|
+
fontWeight: 700,
|
|
392
|
+
color: '#1e40af',
|
|
393
|
+
backgroundColor: '#dbeafe',
|
|
394
|
+
fontVariantNumeric: 'tabular-nums',
|
|
395
|
+
}}
|
|
396
|
+
>
|
|
397
|
+
{formatCell(pivoted.grandTotal)}
|
|
398
|
+
</td>
|
|
399
|
+
)}
|
|
400
|
+
{showRowAverages && (
|
|
401
|
+
<td
|
|
402
|
+
style={{
|
|
403
|
+
padding: '12px 16px',
|
|
404
|
+
textAlign: 'right',
|
|
405
|
+
fontWeight: 700,
|
|
406
|
+
color: '#166534',
|
|
407
|
+
backgroundColor: '#dcfce7',
|
|
408
|
+
fontVariantNumeric: 'tabular-nums',
|
|
409
|
+
}}
|
|
410
|
+
>
|
|
411
|
+
{formatCell(columnTotalsAverage)}
|
|
412
|
+
</td>
|
|
413
|
+
)}
|
|
414
|
+
</tr>
|
|
415
|
+
)}
|
|
416
|
+
</tbody>
|
|
417
|
+
</table>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export default PivotTable;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
import { CheckCircle, Clock, AlertCircle, XCircle } from 'lucide-react';
|
|
2
|
+
import { CheckCircle, Clock, AlertCircle, XCircle, Info } from 'lucide-react';
|
|
3
3
|
|
|
4
4
|
export interface StatusBadgeProps {
|
|
5
|
-
status: 'paid' | 'pending' | 'overdue' | 'cancelled' | 'success' | 'warning' | 'error' | 'info';
|
|
5
|
+
status: 'paid' | 'pending' | 'overdue' | 'cancelled' | 'success' | 'warning' | 'caution' | 'error' | 'info';
|
|
6
6
|
label?: string;
|
|
7
7
|
size?: 'sm' | 'md' | 'lg';
|
|
8
8
|
showIcon?: boolean;
|
|
@@ -29,6 +29,11 @@ const statusConfig = {
|
|
|
29
29
|
defaultLabel: 'Warning',
|
|
30
30
|
className: 'bg-warning-100 text-warning-800',
|
|
31
31
|
},
|
|
32
|
+
caution: {
|
|
33
|
+
icon: Info,
|
|
34
|
+
defaultLabel: 'Caution',
|
|
35
|
+
className: 'bg-warning-100 text-warning-700',
|
|
36
|
+
},
|
|
32
37
|
overdue: {
|
|
33
38
|
icon: AlertCircle,
|
|
34
39
|
defaultLabel: 'Overdue',
|
|
@@ -16,7 +16,7 @@ Text component for consistent typography across the application.
|
|
|
16
16
|
- **Semantic elements**: Render as p, span, div, h1-h6, or label
|
|
17
17
|
- **Size scale**: xs, sm, base, lg, xl, 2xl
|
|
18
18
|
- **Weight options**: normal, medium, semibold, bold
|
|
19
|
-
- **Color variants**: primary, secondary, muted, accent, error, success, warning
|
|
19
|
+
- **Color variants**: primary, secondary, muted, accent, error, success, warning, caution
|
|
20
20
|
- **Text alignment**: left, center, right
|
|
21
21
|
- **Truncation**: Single line truncate or multi-line clamp (1-6 lines)
|
|
22
22
|
- **Transform**: uppercase, lowercase, capitalize
|
|
@@ -61,7 +61,7 @@ import { Text } from 'notebook-ui';
|
|
|
61
61
|
},
|
|
62
62
|
color: {
|
|
63
63
|
control: 'select',
|
|
64
|
-
options: ['primary', 'secondary', 'muted', 'accent', 'error', 'success', 'warning'],
|
|
64
|
+
options: ['primary', 'secondary', 'muted', 'accent', 'error', 'success', 'warning', 'caution'],
|
|
65
65
|
description: 'Text color',
|
|
66
66
|
},
|
|
67
67
|
align: {
|
|
@@ -120,7 +120,9 @@ export const Weights: Story = {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
* All available color variants including
|
|
123
|
+
* All available color variants including `warning` and `caution` colors.
|
|
124
|
+
* - **warning**: Urgent attention needed (brighter amber)
|
|
125
|
+
* - **caution**: Informational, exploratory states like sandbox/demo mode (darker, subdued amber)
|
|
124
126
|
*/
|
|
125
127
|
export const Colors: Story = {
|
|
126
128
|
render: () => (
|
|
@@ -130,15 +132,16 @@ export const Colors: Story = {
|
|
|
130
132
|
<Text color="muted">Muted - Subdued text</Text>
|
|
131
133
|
<Text color="accent">Accent - Branded color</Text>
|
|
132
134
|
<Text color="success">Success - Positive feedback</Text>
|
|
133
|
-
<Text color="warning">Warning -
|
|
135
|
+
<Text color="warning">Warning - Urgent attention</Text>
|
|
136
|
+
<Text color="caution">Caution - Informational, exploratory</Text>
|
|
134
137
|
<Text color="error">Error - Error messages</Text>
|
|
135
138
|
</Stack>
|
|
136
139
|
),
|
|
137
140
|
};
|
|
138
141
|
|
|
139
142
|
/**
|
|
140
|
-
* The warning color is useful for
|
|
141
|
-
*
|
|
143
|
+
* The warning color is useful for urgent alerts, threshold warnings,
|
|
144
|
+
* or status indicators that need immediate attention.
|
|
142
145
|
*/
|
|
143
146
|
export const WarningColor: Story = {
|
|
144
147
|
render: () => (
|
|
@@ -156,6 +159,27 @@ export const WarningColor: Story = {
|
|
|
156
159
|
),
|
|
157
160
|
};
|
|
158
161
|
|
|
162
|
+
/**
|
|
163
|
+
* The caution color is for informational states that need context but aren't alarming.
|
|
164
|
+
* Use for demo/sandbox modes, wash sale notices, or exploratory features.
|
|
165
|
+
* It's calmer than warning - inviting users to explore safely.
|
|
166
|
+
*/
|
|
167
|
+
export const CautionColor: Story = {
|
|
168
|
+
render: () => (
|
|
169
|
+
<Stack spacing="md">
|
|
170
|
+
<Text color="caution" size="lg" weight="semibold">
|
|
171
|
+
Demo Mode: Changes will not be saved
|
|
172
|
+
</Text>
|
|
173
|
+
<Text color="caution">
|
|
174
|
+
This is a sandbox environment for testing
|
|
175
|
+
</Text>
|
|
176
|
+
<Text color="caution" size="sm">
|
|
177
|
+
Wash sale: Cost basis adjusted for tax purposes
|
|
178
|
+
</Text>
|
|
179
|
+
</Stack>
|
|
180
|
+
),
|
|
181
|
+
};
|
|
182
|
+
|
|
159
183
|
export const Alignment: Story = {
|
|
160
184
|
render: () => (
|
|
161
185
|
<Stack spacing="sm" style={{ width: '300px' }}>
|
package/src/components/Text.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'colo
|
|
|
23
23
|
/** Weight variant */
|
|
24
24
|
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
|
|
25
25
|
/** Color variant */
|
|
26
|
-
color?: 'primary' | 'secondary' | 'muted' | 'accent' | 'error' | 'success' | 'warning';
|
|
26
|
+
color?: 'primary' | 'secondary' | 'muted' | 'accent' | 'error' | 'success' | 'warning' | 'caution';
|
|
27
27
|
/** Text alignment */
|
|
28
28
|
align?: 'left' | 'center' | 'right';
|
|
29
29
|
/** Truncate text with ellipsis (single line) */
|
|
@@ -54,9 +54,10 @@ export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'colo
|
|
|
54
54
|
* <Text size="lg" weight="semibold" color="primary">
|
|
55
55
|
* Hello World
|
|
56
56
|
* </Text>
|
|
57
|
-
*
|
|
57
|
+
*
|
|
58
58
|
* <Text color="warning">Warning message</Text>
|
|
59
|
-
*
|
|
59
|
+
* <Text color="caution">Caution message (informational, not alarming)</Text>
|
|
60
|
+
*
|
|
60
61
|
* // With ref
|
|
61
62
|
* const textRef = useRef<HTMLParagraphElement>(null);
|
|
62
63
|
* <Text ref={textRef}>Measurable text</Text>
|
|
@@ -130,6 +131,7 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
|
|
130
131
|
error: 'text-error-600',
|
|
131
132
|
success: 'text-success-600',
|
|
132
133
|
warning: 'text-warning-600',
|
|
134
|
+
caution: 'text-warning-700',
|
|
133
135
|
};
|
|
134
136
|
|
|
135
137
|
const alignClasses = {
|
package/src/components/index.ts
CHANGED
|
@@ -404,6 +404,10 @@ export type {
|
|
|
404
404
|
FrozenRowMode,
|
|
405
405
|
} from './DataGrid';
|
|
406
406
|
|
|
407
|
+
// PivotTable (cross-tabulation display)
|
|
408
|
+
export { default as PivotTable } from './PivotTable';
|
|
409
|
+
export type { PivotTableProps } from './PivotTable';
|
|
410
|
+
|
|
407
411
|
export { default as SwipeActions } from './SwipeActions';
|
|
408
412
|
export type { SwipeActionsProps, SwipeAction } from './SwipeActions';
|
|
409
413
|
|