@juspay/blend-design-system 0.0.37-beta.3 → 0.0.37-beta.4

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 (162) hide show
  1. package/dist/components/AccordionV2/index.d.ts +3 -0
  2. package/dist/components/AvatarV2/avatarV2.utils.d.ts +1 -1
  3. package/dist/components/AvatarV2/index.d.ts +1 -2
  4. package/dist/components/BreadcrumbV2/index.d.ts +10 -0
  5. package/dist/components/ButtonV2/ButtonGroupV2/index.d.ts +1 -0
  6. package/dist/components/ButtonV2/buttonV2.types.d.ts +0 -4
  7. package/dist/components/ButtonV2/index.d.ts +3 -0
  8. package/dist/components/ButtonV2/utils.d.ts +1 -1
  9. package/dist/components/ChartsV2/index.d.ts +5 -0
  10. package/dist/components/CodeEditorV2/CodeEditorV2.d.ts +1 -1
  11. package/dist/components/CodeEditorV2/codeEditorV2.tokens.d.ts +5 -5
  12. package/dist/components/CodeEditorV2/codeEditorV2.types.d.ts +5 -5
  13. package/dist/components/CodeEditorV2/index.d.ts +2 -0
  14. package/dist/components/CodeEditorV2/utils.d.ts +1 -1
  15. package/dist/components/DataTable/DataTable.d.ts +2 -1
  16. package/dist/components/DataTable/PivotTableModal/PivotPreviewPanel.d.ts +3 -0
  17. package/dist/components/DataTable/PivotTableModal/PivotTableIllustration.d.ts +7 -0
  18. package/dist/components/DataTable/PivotTableModal/index.d.ts +3 -0
  19. package/dist/components/DataTable/PivotTableModal/pivotModalStyleTokens.d.ts +123 -0
  20. package/dist/components/DataTable/PivotTableModal/types.d.ts +62 -0
  21. package/dist/components/DataTable/PivotTableModal/utils.d.ts +32 -0
  22. package/dist/components/DataTable/TableBody/types.d.ts +2 -0
  23. package/dist/components/DataTable/TableHeader/types.d.ts +1 -0
  24. package/dist/components/DataTable/index.d.ts +2 -0
  25. package/dist/components/DataTable/types.d.ts +56 -0
  26. package/dist/components/DataTable/utils.d.ts +19 -1
  27. package/dist/components/InputsV2/ChatInputV2/AttachmentDropdown.d.ts +3 -3
  28. package/dist/components/InputsV2/ChatInputV2/ChatInputTagV2.d.ts +2 -2
  29. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.d.ts +4 -4
  30. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.types.d.ts +8 -8
  31. package/dist/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.d.ts +3 -3
  32. package/dist/components/InputsV2/ChatInputV2/MobileChatInputV2.d.ts +2 -2
  33. package/dist/components/InputsV2/ChatInputV2/utils.d.ts +4 -4
  34. package/dist/components/InputsV2/SearchInputV2/utils.d.ts +39 -0
  35. package/dist/components/InputsV2/TextInputV2/TextInputV2.types.d.ts +2 -2
  36. package/dist/components/InputsV2/TextInputV2/index.d.ts +2 -0
  37. package/dist/components/InputsV2/utils/utils.d.ts +1 -1
  38. package/dist/components/KeyValuePairV2/KeyValuePairV2.d.ts +1 -1
  39. package/dist/components/KeyValuePairV2/ResponsiveText.d.ts +2 -2
  40. package/dist/components/KeyValuePairV2/index.d.ts +3 -0
  41. package/dist/components/KeyValuePairV2/keyValuePairV2.types.d.ts +2 -2
  42. package/dist/components/KeyValuePairV2/responsiveTextStyles.d.ts +3 -3
  43. package/dist/components/KeyValuePairV2/utils.d.ts +2 -2
  44. package/dist/components/MenuV2/index.d.ts +1 -0
  45. package/dist/components/MenuV2/menuV2.utils.d.ts +2 -2
  46. package/dist/components/MultiSelectV2/index.d.ts +3 -0
  47. package/dist/components/MultiSelectV2/multiSelectV2.types.d.ts +1 -1
  48. package/dist/components/MultiSelectV2/utils.d.ts +2 -2
  49. package/dist/components/ProgressBarV2/index.d.ts +3 -0
  50. package/dist/components/ProgressBarV2/utils.d.ts +1 -1
  51. package/dist/components/SelectV2/index.d.ts +1 -0
  52. package/dist/components/SelectorV2/CheckboxV2/index.d.ts +4 -0
  53. package/dist/components/SelectorV2/CheckboxV2/utils.d.ts +1 -1
  54. package/dist/components/SelectorV2/RadioV2/index.d.ts +3 -0
  55. package/dist/components/SelectorV2/SwitchV2/index.d.ts +1 -0
  56. package/dist/components/SidebarV2/index.d.ts +5 -0
  57. package/dist/components/SingleSelectV2/SingleSelectV2VirtualList.d.ts +2 -2
  58. package/dist/components/SingleSelectV2/index.d.ts +3 -0
  59. package/dist/components/SingleSelectV2/singleSelectV2.types.d.ts +2 -2
  60. package/dist/components/SingleSelectV2/utils.d.ts +6 -6
  61. package/dist/components/StatCardV2/index.d.ts +10 -1
  62. package/dist/components/StepperV2/index.d.ts +3 -1
  63. package/dist/components/StepperV2/stepperV2.types.d.ts +2 -2
  64. package/dist/components/TabsV2/index.d.ts +3 -1
  65. package/dist/components/TagV2/index.d.ts +3 -0
  66. package/dist/components/TooltipV2/index.d.ts +1 -0
  67. package/dist/components/common/index.d.ts +1 -1
  68. package/dist/main.d.ts +30 -70
  69. package/dist/main.js +87817 -85412
  70. package/dist/{node-CRWdZOVN.js → node-C2uf3sNA.js} +1303 -1300
  71. package/dist/node.js +1 -1
  72. package/dist/tokens.js +1 -1
  73. package/lib/components/AccordionV2/index.ts +3 -0
  74. package/lib/components/AvatarV2/AvatarV2.tsx +2 -2
  75. package/lib/components/AvatarV2/avatarV2.utils.ts +1 -1
  76. package/lib/components/AvatarV2/index.ts +1 -12
  77. package/lib/components/BreadcrumbV2/index.ts +10 -0
  78. package/lib/components/ButtonV2/ButtonGroupV2/index.ts +1 -0
  79. package/lib/components/ButtonV2/ButtonV2.tsx +2 -2
  80. package/lib/components/ButtonV2/LinkButton.tsx +2 -2
  81. package/lib/components/ButtonV2/buttonV2.types.ts +0 -6
  82. package/lib/components/ButtonV2/index.ts +3 -0
  83. package/lib/components/ButtonV2/utils.ts +2 -2
  84. package/lib/components/Charts/BlendChart.tsx +1 -1
  85. package/lib/components/ChartsV2/ChartV2.tsx +3 -2
  86. package/lib/components/ChartsV2/index.ts +5 -0
  87. package/lib/components/CodeEditorV2/CodeEditorV2.tsx +2 -2
  88. package/lib/components/CodeEditorV2/codeEditorV2.dark.tokens.ts +37 -25
  89. package/lib/components/CodeEditorV2/codeEditorV2.light.token.ts +37 -25
  90. package/lib/components/CodeEditorV2/codeEditorV2.tokens.ts +5 -5
  91. package/lib/components/CodeEditorV2/codeEditorV2.types.ts +5 -5
  92. package/lib/components/CodeEditorV2/index.ts +2 -0
  93. package/lib/components/CodeEditorV2/utils.ts +1 -1
  94. package/lib/components/DataTable/DataTable.tsx +148 -4
  95. package/lib/components/DataTable/PivotTableModal/PivotPreviewPanel.tsx +174 -0
  96. package/lib/components/DataTable/PivotTableModal/PivotTableIllustration.tsx +28 -0
  97. package/lib/components/DataTable/PivotTableModal/index.tsx +859 -0
  98. package/lib/components/DataTable/PivotTableModal/pivot-table-illustration.png +0 -0
  99. package/lib/components/DataTable/PivotTableModal/pivotModal.styled.ts +13 -0
  100. package/lib/components/DataTable/PivotTableModal/pivotModalStyleTokens.ts +250 -0
  101. package/lib/components/DataTable/PivotTableModal/types.ts +69 -0
  102. package/lib/components/DataTable/PivotTableModal/utils.ts +360 -0
  103. package/lib/components/DataTable/TableBody/index.tsx +16 -5
  104. package/lib/components/DataTable/TableBody/types.ts +2 -0
  105. package/lib/components/DataTable/TableHeader/index.tsx +6 -3
  106. package/lib/components/DataTable/TableHeader/types.ts +1 -0
  107. package/lib/components/DataTable/index.ts +4 -0
  108. package/lib/components/DataTable/types.ts +57 -0
  109. package/lib/components/DataTable/utils.ts +197 -0
  110. package/lib/components/InputsV2/ChatInputV2/AttachmentDropdown.tsx +3 -3
  111. package/lib/components/InputsV2/ChatInputV2/ChatInputTagV2.tsx +3 -3
  112. package/lib/components/InputsV2/ChatInputV2/ChatInputV2.types.ts +8 -8
  113. package/lib/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.tsx +7 -7
  114. package/lib/components/InputsV2/ChatInputV2/utils.ts +8 -8
  115. package/lib/components/InputsV2/SearchInputV2/utils.ts +14 -1
  116. package/lib/components/InputsV2/TextInputV2/TextInputV2.tsx +3 -3
  117. package/lib/components/InputsV2/TextInputV2/TextInputV2.types.ts +2 -2
  118. package/lib/components/InputsV2/TextInputV2/index.ts +2 -0
  119. package/lib/components/KeyValuePairV2/KeyValuePairV2.tsx +6 -2
  120. package/lib/components/KeyValuePairV2/ResponsiveText.tsx +2 -2
  121. package/lib/components/KeyValuePairV2/index.ts +3 -0
  122. package/lib/components/KeyValuePairV2/keyValuePairV2.types.ts +2 -2
  123. package/lib/components/KeyValuePairV2/responsiveTextStyles.ts +3 -3
  124. package/lib/components/KeyValuePairV2/utils.ts +3 -3
  125. package/lib/components/MenuV2/MenuV2.tsx +2 -2
  126. package/lib/components/MenuV2/MenuV2SubMenu.tsx +2 -2
  127. package/lib/components/MenuV2/index.ts +1 -0
  128. package/lib/components/MenuV2/menuV2.utils.ts +4 -4
  129. package/lib/components/MultiSelectV2/MultiSelectV2.tsx +2 -2
  130. package/lib/components/MultiSelectV2/MultiSelectV2Menu.tsx +5 -2
  131. package/lib/components/MultiSelectV2/index.ts +3 -0
  132. package/lib/components/MultiSelectV2/mobile/MobileMultiSelectV2.tsx +7 -4
  133. package/lib/components/MultiSelectV2/multiSelectV2.types.ts +1 -1
  134. package/lib/components/MultiSelectV2/utils.ts +2 -2
  135. package/lib/components/ProgressBarV2/ProgressBarV2.tsx +5 -2
  136. package/lib/components/ProgressBarV2/index.ts +3 -0
  137. package/lib/components/ProgressBarV2/utils.ts +1 -1
  138. package/lib/components/SelectV2/index.ts +1 -0
  139. package/lib/components/SelectorV2/CheckboxV2/CheckboxV2.tsx +2 -2
  140. package/lib/components/SelectorV2/CheckboxV2/index.ts +4 -0
  141. package/lib/components/SelectorV2/CheckboxV2/utils.ts +1 -1
  142. package/lib/components/SelectorV2/RadioV2/index.ts +3 -0
  143. package/lib/components/SelectorV2/SwitchV2/index.ts +1 -0
  144. package/lib/components/Sidebar/Sidebar.tsx +7 -2
  145. package/lib/components/SidebarV2/index.ts +5 -0
  146. package/lib/components/SingleSelectV2/MobileSingleSelectV2.tsx +2 -2
  147. package/lib/components/SingleSelectV2/SingleSelectV2.tsx +10 -3
  148. package/lib/components/SingleSelectV2/SingleSelectV2Menu.tsx +4 -2
  149. package/lib/components/SingleSelectV2/SingleSelectV2VirtualList.tsx +5 -2
  150. package/lib/components/SingleSelectV2/index.ts +7 -0
  151. package/lib/components/SingleSelectV2/singleSelectV2.types.ts +2 -2
  152. package/lib/components/SingleSelectV2/utils.ts +10 -10
  153. package/lib/components/StatCardV2/index.ts +13 -1
  154. package/lib/components/StepperV2/index.ts +3 -1
  155. package/lib/components/StepperV2/stepperV2.types.ts +2 -2
  156. package/lib/components/TabsV2/index.ts +13 -1
  157. package/lib/components/TagV2/index.ts +3 -0
  158. package/lib/components/TooltipV2/index.ts +1 -0
  159. package/lib/components/common/index.ts +1 -1
  160. package/lib/main.ts +34 -258
  161. package/lib/types/assets.d.ts +24 -0
  162. package/package.json +2 -1
@@ -0,0 +1,13 @@
1
+ import styled from 'styled-components'
2
+ import Block from '../../Primitives/Block/Block'
3
+
4
+ /**
5
+ * Scroll region without visible scrollbars (preview + config panels).
6
+ */
7
+ export const NoScrollbar = styled(Block)`
8
+ scrollbar-width: none;
9
+ -ms-overflow-style: none;
10
+ &::-webkit-scrollbar {
11
+ display: none;
12
+ }
13
+ `
@@ -0,0 +1,250 @@
1
+ import type { FoundationTokenType } from '../../../tokens/theme.token'
2
+ import type { TableTokenType } from '../dataTable.tokens'
3
+
4
+ export type PivotModalStyleTokens = {
5
+ modal: {
6
+ minWidth: string
7
+ maxWidth: string
8
+ maxHeight: string
9
+ bodyPadding: string
10
+ bodyGap: string
11
+ }
12
+ rightPanel: {
13
+ width: string
14
+ padding: string
15
+ background: string
16
+ border: string
17
+ borderRadius: string
18
+ }
19
+ shell: {
20
+ display: 'grid'
21
+ gridTemplateColumns: string
22
+ height: string
23
+ overflow: 'hidden'
24
+ }
25
+ layout: {
26
+ wideDesktopMinWidth: number
27
+ wideDesktopColumns: string
28
+ fallbackColumns: string
29
+ }
30
+ panelPadding: string
31
+ configPanelBackground: string
32
+ previewPanelBackground: string
33
+ text: {
34
+ sectionTitle: {
35
+ fontSize: string
36
+ fontWeight: number
37
+ color: string
38
+ }
39
+ fieldLabel: {
40
+ fontSize: string
41
+ fontWeight: number
42
+ color: string
43
+ }
44
+ checkboxLabel: {
45
+ fontSize: string
46
+ color: string
47
+ }
48
+ }
49
+ spacing: {
50
+ sectionGap: string
51
+ builderIntroGap: string
52
+ stackGap: string
53
+ controlsRowGap: string
54
+ iconBadgeGap: string
55
+ sectionHeaderMarginBottom: string
56
+ }
57
+ dropZone: {
58
+ emptyMinHeight: string
59
+ padding: string
60
+ borderRadius: string
61
+ border: string
62
+ background: string
63
+ }
64
+ chip: {
65
+ borderRadius: string
66
+ padding: string
67
+ hoverShadow: string
68
+ hoverTranslateY: string
69
+ transition: string
70
+ }
71
+ iconBadge: {
72
+ size: string
73
+ borderRadius: string
74
+ glyphFontSize: string | number
75
+ glyphFontWeight: number
76
+ }
77
+ sectionLabel: {
78
+ fontWeight: number
79
+ fontSize: string | number
80
+ textTransform: 'uppercase'
81
+ letterSpacing: string
82
+ }
83
+ sectionCount: {
84
+ fontSize: string | number
85
+ color: string
86
+ }
87
+ emptyState: {
88
+ titleFontSize: string | number
89
+ titleColor: string
90
+ exampleFontSize: string | number
91
+ exampleColor: string
92
+ padding: string
93
+ titleMarginBottom: string
94
+ }
95
+ fieldRowLabel: {
96
+ fontSize: string | number
97
+ color: string
98
+ marginBottom: string
99
+ }
100
+ removeButton: {
101
+ minWidth: string
102
+ padding: string
103
+ borderRadius: string
104
+ border: string
105
+ background: string
106
+ hoverBackground: string
107
+ iconColor: string
108
+ iconSize: number
109
+ }
110
+ menuCheckIconSize: number
111
+ preview: {
112
+ headerMarginBottom: string
113
+ titleFontWeight: number
114
+ titleFontSize: string | number
115
+ titleMarginBottom: string
116
+ metaFontSize: string
117
+ }
118
+ bodyLineHeight: string
119
+ }
120
+
121
+ /**
122
+ * Layout and typography for the pivot modal, derived from foundation tokens + responsive table tokens.
123
+ * Section chips use `colors.primary` only (see color.tokens).
124
+ */
125
+ export const getPivotModalStyleTokens = (
126
+ f: FoundationTokenType,
127
+ tableToken: TableTokenType
128
+ ): PivotModalStyleTokens => {
129
+ const { dataTable, header } = tableToken
130
+ const cell = dataTable.table.body.cell
131
+
132
+ const rightPanelMinWidth = '360px'
133
+
134
+ return {
135
+ modal: {
136
+ minWidth: 'min(95vw, 120rem)',
137
+ maxWidth: '95vw',
138
+ maxHeight: '92vh',
139
+ bodyPadding: f.unit[20],
140
+ bodyGap: f.unit[16],
141
+ },
142
+ rightPanel: {
143
+ width: rightPanelMinWidth,
144
+ padding: f.unit[20],
145
+ background: f.colors.gray[0] as string,
146
+ border: `${f.border.width[1]} solid ${f.colors.gray[200]}`,
147
+ borderRadius: f.border.radius[8],
148
+ },
149
+ shell: {
150
+ display: 'grid',
151
+ gridTemplateColumns: `minmax(0, 1fr) ${rightPanelMinWidth}`,
152
+ height: `min(${dataTable.maxHeight}, 85vh)`,
153
+ overflow: 'hidden',
154
+ },
155
+ layout: {
156
+ wideDesktopMinWidth: 1800,
157
+ wideDesktopColumns: `minmax(0, 4fr) minmax(${rightPanelMinWidth}, 1fr)`,
158
+ fallbackColumns: `minmax(0, 1fr) ${rightPanelMinWidth}`,
159
+ },
160
+ panelPadding: f.unit[16],
161
+ configPanelBackground: f.colors.gray[50] as string,
162
+ previewPanelBackground: dataTable.table.body.backgroundColor as string,
163
+ text: {
164
+ sectionTitle: {
165
+ fontSize: f.font.size.body.md.fontSize,
166
+ fontWeight: f.font.weight[600],
167
+ color: f.colors.gray[800] as string,
168
+ },
169
+ fieldLabel: {
170
+ fontSize: f.font.size.body.md.fontSize,
171
+ fontWeight: f.font.weight[500],
172
+ color: f.colors.gray[800] as string,
173
+ },
174
+ checkboxLabel: {
175
+ fontSize: f.font.size.body.sm.fontSize,
176
+ color: f.colors.gray[600] as string,
177
+ },
178
+ },
179
+ spacing: {
180
+ sectionGap: f.unit[20],
181
+ builderIntroGap: f.unit[20],
182
+ stackGap: f.unit[8],
183
+ controlsRowGap: f.unit[8],
184
+ iconBadgeGap: f.unit[8],
185
+ sectionHeaderMarginBottom: f.unit[12],
186
+ },
187
+ dropZone: {
188
+ emptyMinHeight: f.unit[80],
189
+ padding: f.unit[12],
190
+ borderRadius: f.border.radius[8],
191
+ border: `2px dashed ${f.colors.gray[200]}`,
192
+ background: f.colors.gray[0] as string,
193
+ },
194
+ chip: {
195
+ borderRadius: f.border.radius[6],
196
+ padding: `${f.unit[8]} ${f.unit[12]}`,
197
+ hoverShadow: f.shadows.sm,
198
+ hoverTranslateY: `-${f.unit[1]}`,
199
+ transition: 'box-shadow 0.2s ease, transform 0.2s ease',
200
+ },
201
+ iconBadge: {
202
+ size: f.unit[24],
203
+ borderRadius: f.border.radius[4],
204
+ glyphFontSize: f.font.size.body.sm.fontSize,
205
+ glyphFontWeight: 700,
206
+ },
207
+ sectionLabel: {
208
+ fontWeight: 600,
209
+ fontSize: f.font.size.body.sm.fontSize,
210
+ textTransform: 'uppercase',
211
+ letterSpacing: f.unit[0.5],
212
+ },
213
+ sectionCount: {
214
+ fontSize: f.font.size.body.sm.fontSize,
215
+ color: f.colors.gray[600] as string,
216
+ },
217
+ emptyState: {
218
+ titleFontSize: f.font.size.body.md.fontSize,
219
+ titleColor: f.colors.gray[800] as string,
220
+ exampleFontSize: f.font.size.body.sm.fontSize,
221
+ exampleColor: f.colors.gray[400] as string,
222
+ padding: f.unit[16],
223
+ titleMarginBottom: f.unit[8],
224
+ },
225
+ fieldRowLabel: {
226
+ fontSize: f.font.size.body.sm.fontSize,
227
+ color: f.colors.gray[600] as string,
228
+ marginBottom: f.unit[8],
229
+ },
230
+ removeButton: {
231
+ minWidth: f.unit[28],
232
+ padding: f.unit[4],
233
+ borderRadius: f.border.radius[4],
234
+ border: `${f.border.width[1]} solid ${f.colors.gray[300]}`,
235
+ background: f.colors.gray[0] as string,
236
+ hoverBackground: f.colors.gray[100] as string,
237
+ iconColor: f.colors.gray[600] as string,
238
+ iconSize: Number.parseInt(String(f.unit[14]), 10) || 14,
239
+ },
240
+ menuCheckIconSize: Number.parseInt(String(f.unit[16]), 10) || 16,
241
+ preview: {
242
+ headerMarginBottom: f.unit[16],
243
+ titleFontWeight: 600,
244
+ titleFontSize: f.font.size.body.md.fontSize,
245
+ titleMarginBottom: f.unit[4],
246
+ metaFontSize: cell.fontSize as string,
247
+ },
248
+ bodyLineHeight: header.description.lineHeight as string,
249
+ } as PivotModalStyleTokens
250
+ }
@@ -0,0 +1,69 @@
1
+ import { ReactNode } from 'react'
2
+ import type { TableTokenType } from '../dataTable.tokens'
3
+ import { ColumnDefinition, PivotAggregationType } from '../types'
4
+ import type { PivotModalStyleTokens } from './pivotModalStyleTokens'
5
+
6
+ export type PivotValueConfig<T extends Record<string, unknown>> = {
7
+ field: keyof T
8
+ aggregation: PivotAggregationType
9
+ }
10
+
11
+ export type PivotTableConfig<T extends Record<string, unknown>> = {
12
+ rows: Array<keyof T>
13
+ columns: Array<keyof T>
14
+ values: PivotValueConfig<T>[]
15
+ }
16
+
17
+ export type PivotTableModalProps<T extends Record<string, unknown>> = {
18
+ isOpen: boolean
19
+ onClose: () => void
20
+ data: T[]
21
+ columns: ColumnDefinition<T>[]
22
+ title?: string
23
+ description?: string
24
+ showExport?: boolean
25
+ initialConfig?: Partial<PivotTableConfig<T>>
26
+ previewColumns?: PivotPreviewColumn[]
27
+ previewRows?: PivotPreviewRow[]
28
+ onConfigChange?: (config: PivotTableConfig<T>) => void
29
+ onExport?: (config: PivotTableConfig<T>) => void
30
+ trigger?: ReactNode
31
+ /**
32
+ * Callback when trigger is clicked. Use this to open the modal.
33
+ */
34
+ onTriggerClick?: () => void
35
+ /**
36
+ * Configure which aggregation operations are available in the Values section.
37
+ * If not provided, all operations will be shown.
38
+ * @example ['sum', 'count', 'average'] // Show only sum, count, and average
39
+ */
40
+ availableAggregations?: PivotAggregationType[]
41
+ }
42
+
43
+ export type PivotPreviewColumn = {
44
+ key: string
45
+ label: string
46
+ }
47
+
48
+ export type PivotPreviewRow = Record<string, unknown> & {
49
+ __pivotId: string
50
+ __pivotRowType?: 'data' | 'subtotal' | 'grand_total'
51
+ }
52
+
53
+ export type PivotPreviewPanelProps = {
54
+ pivot: PivotModalStyleTokens
55
+ tableToken: TableTokenType
56
+ showExport: boolean
57
+ previewRows?: PivotPreviewRow[]
58
+ previewColumns?: PivotPreviewColumn[]
59
+ previewTableColumns: ColumnDefinition<Record<string, unknown>>[]
60
+ onExport: () => void
61
+ hasValues: boolean
62
+ }
63
+
64
+ export type PivotFieldConfig = {
65
+ field: string
66
+ showTotal: boolean
67
+ aggregation?: PivotAggregationType
68
+ header?: string
69
+ }
@@ -0,0 +1,360 @@
1
+ import { ColumnDefinition, PivotAggregationType } from '../types'
2
+ import { PivotPreviewColumn, PivotPreviewRow, PivotValueConfig } from './types'
3
+
4
+ export const normalizePivotValue = (value: unknown): string => {
5
+ if (value == null) return 'N/A'
6
+ if (typeof value === 'string' || typeof value === 'number') {
7
+ return String(value)
8
+ }
9
+ if (Array.isArray(value)) {
10
+ return value.map((item) => normalizePivotValue(item)).join(', ')
11
+ }
12
+ if (typeof value === 'object') {
13
+ const objectValue = value as Record<string, unknown>
14
+ if ('text' in objectValue) return String(objectValue.text)
15
+ if ('label' in objectValue) return String(objectValue.label)
16
+ if ('value' in objectValue) return String(objectValue.value)
17
+ if ('selectedValue' in objectValue)
18
+ return String(objectValue.selectedValue)
19
+ if ('values' in objectValue && Array.isArray(objectValue.values)) {
20
+ return objectValue.values
21
+ .map((item) => normalizePivotValue(item))
22
+ .join(', ')
23
+ }
24
+ if ('name' in objectValue) return String(objectValue.name)
25
+ try {
26
+ return JSON.stringify(objectValue)
27
+ } catch {
28
+ return 'N/A'
29
+ }
30
+ }
31
+ return String(value)
32
+ }
33
+
34
+ const truncatePivotLabel = (value: string, maxLength = 36): string => {
35
+ if (value.length <= maxLength) return value
36
+ return `${value.slice(0, maxLength - 3)}...`
37
+ }
38
+
39
+ const toNumeric = (value: unknown): number => {
40
+ if (typeof value === 'number') return value
41
+ const parsed = Number(
42
+ normalizePivotValue(value)
43
+ .replace(/,/g, '')
44
+ .replace(/[^\d.-]/g, '')
45
+ )
46
+ return Number.isNaN(parsed) ? 0 : parsed
47
+ }
48
+
49
+ const aggregate = (
50
+ rows: Array<Record<string, unknown>>,
51
+ field: string,
52
+ aggregation: PivotAggregationType
53
+ ): number => {
54
+ if (aggregation === PivotAggregationType.COUNT) {
55
+ return rows.length
56
+ }
57
+
58
+ const values = rows.map((row) => toNumeric(row[field]))
59
+ if (!values.length) return 0
60
+
61
+ switch (aggregation) {
62
+ case PivotAggregationType.SUM:
63
+ return values.reduce((sum, value) => sum + value, 0)
64
+ case PivotAggregationType.AVERAGE:
65
+ case PivotAggregationType.MEAN:
66
+ return values.reduce((sum, value) => sum + value, 0) / values.length
67
+ case PivotAggregationType.MEDIAN: {
68
+ const sorted = [...values].sort((a, b) => a - b)
69
+ const middle = Math.floor(sorted.length / 2)
70
+ return sorted.length % 2 === 0
71
+ ? (sorted[middle - 1] + sorted[middle]) / 2
72
+ : sorted[middle]
73
+ }
74
+ case PivotAggregationType.MIN:
75
+ return Math.min(...values)
76
+ case PivotAggregationType.MAX:
77
+ return Math.max(...values)
78
+ default:
79
+ return values.reduce((sum, value) => sum + value, 0)
80
+ }
81
+ }
82
+
83
+ export const getPivotFieldOptions = <T extends Record<string, unknown>>(
84
+ columns: ColumnDefinition<T>[]
85
+ ): Array<{ key: string; label: string }> =>
86
+ columns.map((column) => ({
87
+ key: String(column.field),
88
+ label: column.header || String(column.field),
89
+ }))
90
+
91
+ export const isPivotNumericValue = (value: unknown): boolean => {
92
+ if (typeof value === 'number') return Number.isFinite(value)
93
+ if (value == null) return false
94
+ const normalized = normalizePivotValue(value)
95
+ .replace(/,/g, '')
96
+ .replace(/[^\d.-]/g, '')
97
+ .trim()
98
+ if (!normalized) return false
99
+ const parsed = Number(normalized)
100
+ return Number.isFinite(parsed)
101
+ }
102
+
103
+ export const getSupportedAggregationsForField = <
104
+ T extends Record<string, unknown>,
105
+ >(
106
+ data: T[],
107
+ field: keyof T,
108
+ allowedAggregations: PivotAggregationType[],
109
+ isNumericColumn = false
110
+ ): PivotAggregationType[] => {
111
+ const hasNumericValue =
112
+ isNumericColumn || data.some((row) => isPivotNumericValue(row[field]))
113
+
114
+ return allowedAggregations.filter((aggregation) => {
115
+ if (aggregation === PivotAggregationType.COUNT) return true
116
+ return hasNumericValue
117
+ })
118
+ }
119
+
120
+ /**
121
+ * Builds the rectangular grid the modal preview renders as a {@link DataTable}.
122
+ *
123
+ * - **Columns:** First column key `__rowLabel` — header is joined row field names
124
+ * (or `All Rows`). Each unique combination of **column** dimension values
125
+ * becomes a group; for each group, every **value** config adds one column
126
+ * (`columnKey__field__aggregation`).
127
+ * - **Rows:** One row per distinct row-dimension key; cells are aggregated from
128
+ * raw rows that match that row key and column key.
129
+ * - **Grand total:** Appended last with `__pivotRowType: 'grand_total'` and
130
+ * `__rowLabel` `"Grand Total"`; each metric is aggregated over the full
131
+ * (already filtered) `data` for that column bucket.
132
+ */
133
+ export const buildPivotPreview = <T extends Record<string, unknown>>(
134
+ data: T[],
135
+ rowFields: Array<keyof T> | Array<{ field: keyof T; showTotal?: boolean }>,
136
+ columnFields:
137
+ | Array<keyof T>
138
+ | Array<{ field: keyof T; showTotal?: boolean }>,
139
+ valueConfigs: PivotValueConfig<T>[],
140
+ fieldLabelByKey: Record<string, string> = {}
141
+ ): { columns: PivotPreviewColumn[]; rows: PivotPreviewRow[] } => {
142
+ if (!valueConfigs.length) return { columns: [], rows: [] }
143
+
144
+ const rowFieldConfigs = rowFields.map((field) =>
145
+ typeof field === 'object' && field !== null
146
+ ? { field: field.field, showTotal: field.showTotal === true }
147
+ : { field, showTotal: false }
148
+ ) as Array<{ field: keyof T; showTotal: boolean }>
149
+ const columnFieldConfigs = columnFields.map((field) =>
150
+ typeof field === 'object' && field !== null
151
+ ? { field: field.field, showTotal: field.showTotal === true }
152
+ : { field, showTotal: false }
153
+ ) as Array<{ field: keyof T; showTotal: boolean }>
154
+
155
+ const normalizedRowFields = rowFields.map((field) =>
156
+ typeof field === 'object' && field !== null ? field.field : field
157
+ ) as Array<keyof T>
158
+ const normalizedColumnFields = columnFields.map((field) =>
159
+ typeof field === 'object' && field !== null ? field.field : field
160
+ ) as Array<keyof T>
161
+
162
+ const shouldShowRowGrandTotal =
163
+ normalizedRowFields.length === 0 ||
164
+ rowFieldConfigs.some((r) => r.showTotal)
165
+ const shouldShowColumnTotals =
166
+ normalizedColumnFields.length > 0 &&
167
+ columnFieldConfigs.some((c) => c.showTotal)
168
+
169
+ type PivotRowNode = {
170
+ rows: T[]
171
+ children: Map<string, PivotRowNode>
172
+ }
173
+ const rowTree: PivotRowNode = { rows: [], children: new Map() }
174
+ const columnKeySet = new Set<string>()
175
+
176
+ data.forEach((row) => {
177
+ let node = rowTree
178
+ node.rows.push(row)
179
+ normalizedRowFields.forEach((field) => {
180
+ const part = normalizePivotValue(row[field])
181
+ const next = node.children.get(part)
182
+ if (next) {
183
+ node = next
184
+ node.rows.push(row)
185
+ } else {
186
+ const created: PivotRowNode = {
187
+ rows: [row],
188
+ children: new Map(),
189
+ }
190
+ node.children.set(part, created)
191
+ node = created
192
+ }
193
+ })
194
+
195
+ const columnKey = normalizedColumnFields.length
196
+ ? normalizedColumnFields
197
+ .map((field) => normalizePivotValue(row[field]))
198
+ .join(' | ')
199
+ : 'All Columns'
200
+
201
+ columnKeySet.add(columnKey)
202
+ })
203
+
204
+ const orderedColumnKeys = Array.from(columnKeySet).sort((a, b) =>
205
+ a.localeCompare(b)
206
+ )
207
+ const getFieldLabel = (key: keyof T): string =>
208
+ fieldLabelByKey[String(key)] || String(key)
209
+
210
+ const rowLabel = normalizedRowFields.length
211
+ ? normalizedRowFields.map((field) => getFieldLabel(field)).join(' / ')
212
+ : 'All Rows'
213
+
214
+ const previewColumns: PivotPreviewColumn[] = [
215
+ { key: '__rowLabel', label: rowLabel },
216
+ ...orderedColumnKeys.flatMap((columnKey) =>
217
+ valueConfigs.map((valueConfig, valueIndex) => ({
218
+ key: `${columnKey}__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`,
219
+ label: `${truncatePivotLabel(columnKey)} | ${valueConfig.aggregation}(${getFieldLabel(valueConfig.field)})`,
220
+ }))
221
+ ),
222
+ ...(shouldShowColumnTotals
223
+ ? valueConfigs.map((valueConfig, valueIndex) => ({
224
+ key: `__columnTotal__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`,
225
+ label: `Total | ${valueConfig.aggregation}(${getFieldLabel(valueConfig.field)})`,
226
+ }))
227
+ : []),
228
+ ]
229
+
230
+ let idCounter = 0
231
+ const computeMetricCells = (
232
+ targetRow: PivotPreviewRow,
233
+ groupedRows: T[]
234
+ ) => {
235
+ orderedColumnKeys.forEach((columnKey) => {
236
+ const columnRows = normalizedColumnFields.length
237
+ ? groupedRows.filter(
238
+ (groupRow) =>
239
+ normalizedColumnFields
240
+ .map((field) =>
241
+ normalizePivotValue(groupRow[field])
242
+ )
243
+ .join(' | ') === columnKey
244
+ )
245
+ : groupedRows
246
+
247
+ valueConfigs.forEach((valueConfig, valueIndex) => {
248
+ const key = `${columnKey}__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
249
+ targetRow[key] = Number(
250
+ aggregate(
251
+ columnRows as Array<Record<string, unknown>>,
252
+ String(valueConfig.field),
253
+ valueConfig.aggregation
254
+ ).toFixed(2)
255
+ )
256
+ })
257
+ })
258
+
259
+ if (shouldShowColumnTotals) {
260
+ valueConfigs.forEach((valueConfig, valueIndex) => {
261
+ const key = `__columnTotal__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
262
+ targetRow[key] = Number(
263
+ aggregate(
264
+ groupedRows as Array<Record<string, unknown>>,
265
+ String(valueConfig.field),
266
+ valueConfig.aggregation
267
+ ).toFixed(2)
268
+ )
269
+ })
270
+ }
271
+ }
272
+
273
+ const rows: PivotPreviewRow[] = []
274
+ const traverse = (node: PivotRowNode, depth: number, parts: string[]) => {
275
+ if (depth === normalizedRowFields.length) {
276
+ const rowKey = parts.length ? parts.join(' | ') : 'All Rows'
277
+ const row: PivotPreviewRow = {
278
+ __pivotId: `pivot-row-${idCounter++}`,
279
+ __rowLabel: truncatePivotLabel(rowKey, 60),
280
+ __pivotRowType: 'data',
281
+ }
282
+ computeMetricCells(row, node.rows)
283
+ rows.push(row)
284
+ return
285
+ }
286
+
287
+ const childKeys = Array.from(node.children.keys()).sort((a, b) =>
288
+ a.localeCompare(b)
289
+ )
290
+ childKeys.forEach((key) => {
291
+ const child = node.children.get(key)
292
+ if (!child) return
293
+ traverse(child, depth + 1, [...parts, key])
294
+ })
295
+
296
+ if (
297
+ normalizedRowFields.length > 1 &&
298
+ depth < normalizedRowFields.length - 1 &&
299
+ rowFieldConfigs[depth]?.showTotal
300
+ ) {
301
+ const label = `${parts.join(' / ')} Total`
302
+ const subtotalRow: PivotPreviewRow = {
303
+ __pivotId: `pivot-subtotal-${idCounter++}`,
304
+ __rowLabel: truncatePivotLabel(label, 60),
305
+ __pivotRowType: 'subtotal',
306
+ }
307
+ computeMetricCells(subtotalRow, node.rows)
308
+ rows.push(subtotalRow)
309
+ }
310
+ }
311
+
312
+ traverse(rowTree, 0, [])
313
+
314
+ if (shouldShowRowGrandTotal) {
315
+ const grandTotalRow: PivotPreviewRow = {
316
+ __pivotId: 'pivot-grand-total',
317
+ __rowLabel: 'Grand Total',
318
+ __pivotRowType: 'grand_total',
319
+ }
320
+
321
+ orderedColumnKeys.forEach((columnKey) => {
322
+ const columnRows = normalizedColumnFields.length
323
+ ? data.filter(
324
+ (row) =>
325
+ normalizedColumnFields
326
+ .map((field) => normalizePivotValue(row[field]))
327
+ .join(' | ') === columnKey
328
+ )
329
+ : data
330
+ valueConfigs.forEach((valueConfig, valueIndex) => {
331
+ const key = `${columnKey}__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
332
+ grandTotalRow[key] = Number(
333
+ aggregate(
334
+ columnRows as Array<Record<string, unknown>>,
335
+ String(valueConfig.field),
336
+ valueConfig.aggregation
337
+ ).toFixed(2)
338
+ )
339
+ })
340
+ })
341
+ if (shouldShowColumnTotals) {
342
+ valueConfigs.forEach((valueConfig, valueIndex) => {
343
+ const key = `__columnTotal__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
344
+ grandTotalRow[key] = Number(
345
+ aggregate(
346
+ data as Array<Record<string, unknown>>,
347
+ String(valueConfig.field),
348
+ valueConfig.aggregation
349
+ ).toFixed(2)
350
+ )
351
+ })
352
+ }
353
+ rows.push(grandTotalRow)
354
+ }
355
+
356
+ return {
357
+ columns: previewColumns,
358
+ rows,
359
+ }
360
+ }