@toolpath/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/index.d.ts +1174 -0
  4. package/dist/index.js +5934 -0
  5. package/package.json +68 -0
  6. package/src/attribution/action.tsx +13 -0
  7. package/src/attribution/at.tsx +28 -0
  8. package/src/attribution/attribution.tsx +16 -0
  9. package/src/attribution/by.tsx +21 -0
  10. package/src/attribution/format-time.ts +48 -0
  11. package/src/attribution/index.ts +20 -0
  12. package/src/badge/badge.tsx +38 -0
  13. package/src/badge/index.ts +1 -0
  14. package/src/breadcrumbs/breadcrumbs-context.tsx +26 -0
  15. package/src/breadcrumbs/breadcrumbs.tsx +24 -0
  16. package/src/breadcrumbs/index.ts +12 -0
  17. package/src/breadcrumbs/item.tsx +85 -0
  18. package/src/button/animated-ellipsis.tsx +44 -0
  19. package/src/button/button.tsx +168 -0
  20. package/src/button/index.ts +1 -0
  21. package/src/callout/callout.tsx +202 -0
  22. package/src/callout/index.ts +2 -0
  23. package/src/callout/use-dismissed-callouts.ts +78 -0
  24. package/src/card/card.tsx +19 -0
  25. package/src/card/index.ts +2 -0
  26. package/src/checkbox/checkbox.tsx +76 -0
  27. package/src/checkbox/index.ts +1 -0
  28. package/src/combobox/button.tsx +99 -0
  29. package/src/combobox/combobox-context.tsx +42 -0
  30. package/src/combobox/combobox.tsx +60 -0
  31. package/src/combobox/empty.tsx +21 -0
  32. package/src/combobox/group-label.tsx +20 -0
  33. package/src/combobox/group.tsx +11 -0
  34. package/src/combobox/icon.tsx +11 -0
  35. package/src/combobox/index.tsx +64 -0
  36. package/src/combobox/input.tsx +77 -0
  37. package/src/combobox/item-indicator.tsx +16 -0
  38. package/src/combobox/item.tsx +58 -0
  39. package/src/combobox/list.tsx +19 -0
  40. package/src/combobox/popover.tsx +62 -0
  41. package/src/combobox/trigger.tsx +33 -0
  42. package/src/combobox/value.tsx +12 -0
  43. package/src/common/index.ts +1 -0
  44. package/src/common/svg.tsx +17 -0
  45. package/src/dialog/actions.tsx +17 -0
  46. package/src/dialog/confirm-button.tsx +25 -0
  47. package/src/dialog/dialog-context.tsx +21 -0
  48. package/src/dialog/dialog.tsx +37 -0
  49. package/src/dialog/dismiss-button.tsx +29 -0
  50. package/src/dialog/index.ts +37 -0
  51. package/src/dialog/message.tsx +20 -0
  52. package/src/dialog/provider.tsx +102 -0
  53. package/src/dialog/title.tsx +20 -0
  54. package/src/editable-cell/editable-cell.tsx +307 -0
  55. package/src/editable-cell/index.ts +7 -0
  56. package/src/editable-cell/use-editable-grid.tsx +296 -0
  57. package/src/field/field.tsx +22 -0
  58. package/src/field/index.ts +2 -0
  59. package/src/helpers/cn.ts +6 -0
  60. package/src/helpers/index.ts +1 -0
  61. package/src/icon-button/icon-button.tsx +126 -0
  62. package/src/icon-button/index.ts +2 -0
  63. package/src/index.ts +32 -0
  64. package/src/input/index.ts +1 -0
  65. package/src/input/input.tsx +276 -0
  66. package/src/link/index.ts +4 -0
  67. package/src/link/link-context.tsx +33 -0
  68. package/src/link/link.tsx +24 -0
  69. package/src/menu/divider.tsx +17 -0
  70. package/src/menu/index.ts +40 -0
  71. package/src/menu/item.tsx +149 -0
  72. package/src/menu/menu-context.tsx +24 -0
  73. package/src/menu/menu.tsx +14 -0
  74. package/src/menu/popover.tsx +69 -0
  75. package/src/menu/submenu-trigger.tsx +48 -0
  76. package/src/menu/submenu.tsx +20 -0
  77. package/src/menu/trigger.tsx +43 -0
  78. package/src/notification/index.ts +16 -0
  79. package/src/notification/list.tsx +123 -0
  80. package/src/notification/provider.tsx +75 -0
  81. package/src/page-header/index.ts +2 -0
  82. package/src/page-header/page-header.tsx +14 -0
  83. package/src/pagination/index.ts +1 -0
  84. package/src/pagination/pagination.tsx +136 -0
  85. package/src/panels/collapsed.tsx +32 -0
  86. package/src/panels/group.tsx +27 -0
  87. package/src/panels/index.ts +57 -0
  88. package/src/panels/layout-context.ts +5 -0
  89. package/src/panels/orientation-context.tsx +5 -0
  90. package/src/panels/panel.tsx +95 -0
  91. package/src/panels/separator.tsx +44 -0
  92. package/src/pin/index.ts +1 -0
  93. package/src/pin/pin.tsx +99 -0
  94. package/src/progress-bar/index.ts +1 -0
  95. package/src/progress-bar/progress-bar.tsx +48 -0
  96. package/src/quantity-input/index.ts +1 -0
  97. package/src/quantity-input/quantity-input.tsx +158 -0
  98. package/src/scroll-area/index.ts +1 -0
  99. package/src/scroll-area/scroll-area.tsx +82 -0
  100. package/src/slider/index.ts +1 -0
  101. package/src/slider/slider.tsx +75 -0
  102. package/src/star/index.ts +1 -0
  103. package/src/star/star.tsx +99 -0
  104. package/src/switch/index.ts +1 -0
  105. package/src/switch/switch.tsx +33 -0
  106. package/src/table/body.tsx +15 -0
  107. package/src/table/cell-context.tsx +48 -0
  108. package/src/table/cell.tsx +83 -0
  109. package/src/table/group-empty-state.tsx +28 -0
  110. package/src/table/group-header.tsx +62 -0
  111. package/src/table/header-cell.tsx +158 -0
  112. package/src/table/header-row.tsx +28 -0
  113. package/src/table/index.ts +56 -0
  114. package/src/table/is-group-empty-state.ts +17 -0
  115. package/src/table/is-group-header.ts +5 -0
  116. package/src/table/row-context.tsx +61 -0
  117. package/src/table/row-disabled-context.tsx +7 -0
  118. package/src/table/row.tsx +98 -0
  119. package/src/table/table-context.tsx +127 -0
  120. package/src/table/table-empty.tsx +57 -0
  121. package/src/table/table.tsx +547 -0
  122. package/src/table/tree-icon.tsx +147 -0
  123. package/src/table/use-checkbox.ts +200 -0
  124. package/src/table/use-column-layout.ts +117 -0
  125. package/src/table/use-groups.ts +134 -0
  126. package/src/table/use-keyboard-navigation.ts +274 -0
  127. package/src/table/use-sort-functions.ts +90 -0
  128. package/src/tabs/index.ts +20 -0
  129. package/src/tabs/list.tsx +29 -0
  130. package/src/tabs/tab.tsx +141 -0
  131. package/src/tabs/tabs-context.tsx +38 -0
  132. package/src/tabs/tabs.tsx +27 -0
  133. package/src/textarea/index.ts +1 -0
  134. package/src/textarea/textarea.tsx +88 -0
  135. package/src/toast/index.ts +19 -0
  136. package/src/toast/list.tsx +133 -0
  137. package/src/toast/provider.tsx +36 -0
  138. package/src/toggle/index.ts +17 -0
  139. package/src/toggle/indicator.tsx +27 -0
  140. package/src/toggle/item.tsx +54 -0
  141. package/src/toggle/toggle-context.tsx +68 -0
  142. package/src/toggle/toggle.tsx +124 -0
  143. package/src/toggle/use-indicator.tsx +69 -0
  144. package/src/tooltip/index.ts +1 -0
  145. package/src/tooltip/tooltip.tsx +122 -0
  146. package/theme.css +70 -0
@@ -0,0 +1,200 @@
1
+ import { useState } from 'react'
2
+ import { Identifier, TableNode } from '@table-library/react-table-library/types/table'
3
+ import { CheckboxChangeEvent } from '../checkbox'
4
+
5
+ export type UseCheckboxReturn = {
6
+ isChecked: (item: TableNode) => boolean
7
+ isIndeterminate: (item: TableNode) => boolean
8
+ handleCheck: (item: TableNode, checked: boolean, event?: CheckboxChangeEvent) => void
9
+ checkedItems: Array<Identifier>
10
+ }
11
+
12
+ export const useCheckbox = (
13
+ items: Array<TableNode>,
14
+ initialState?: Array<Identifier>,
15
+ ): UseCheckboxReturn => {
16
+ const [checkedRows, setCheckedRows] = useState<Array<Identifier>>(initialState || [])
17
+ const [lastCheckedId, setLastCheckedId] = useState<Identifier | null>(null)
18
+
19
+ // Flatten the tree into a list in visual order
20
+ const flattenItems = (nodes: Array<TableNode>): Array<TableNode> => {
21
+ return nodes.flatMap((node) => [node, ...flattenItems(node.nodes ?? [])])
22
+ }
23
+
24
+ // Find an item by ID in the tree
25
+ const findItemById = (items: Array<TableNode>, id: Identifier): TableNode | undefined => {
26
+ return items.reduce<TableNode | undefined>((found, item) => {
27
+ if (found) {
28
+ return found
29
+ }
30
+ if (item.id === id) {
31
+ return item
32
+ }
33
+ if (item.nodes?.length) {
34
+ return findItemById(item.nodes, id)
35
+ }
36
+ return undefined
37
+ }, undefined)
38
+ }
39
+
40
+ // Recursively get all descendant IDs from a TreeItem
41
+ const getAllDescendantIds = (item: TableNode): Array<Identifier> => {
42
+ if (!item.nodes?.length) {
43
+ return []
44
+ }
45
+ return item.nodes
46
+ .filter((child) => child && child.id != null)
47
+ .flatMap((child) => [child.id, ...getAllDescendantIds(child)])
48
+ }
49
+
50
+ // Get all ancestor IDs for an item
51
+ const getAllAncestorIds = (
52
+ items: Array<TableNode>,
53
+ targetId: Identifier,
54
+ path: Array<Identifier> = [],
55
+ ): Array<Identifier> => {
56
+ return items.reduce<Array<Identifier>>((result, item) => {
57
+ if (result.length > 0) {
58
+ return result
59
+ }
60
+ if (item.id === targetId) {
61
+ return path
62
+ }
63
+ if (item.nodes?.length) {
64
+ const found = getAllAncestorIds(item.nodes, targetId, [...path, item.id])
65
+ if (found.length > 0 || item.nodes.some((n) => n.id === targetId)) {
66
+ return found.length > 0 ? found : [...path, item.id]
67
+ }
68
+ }
69
+ return result
70
+ }, [])
71
+ }
72
+
73
+ // Get ancestors that should be auto-checked (all their descendants are checked)
74
+ const getAncestorsToAutoCheck = (
75
+ items: Array<TableNode>,
76
+ targetId: Identifier,
77
+ newCheckedIds: Array<Identifier>,
78
+ ): Array<Identifier> => {
79
+ const ancestorIds = getAllAncestorIds(items, targetId)
80
+
81
+ // Walk from innermost ancestor to outermost, stop when one fails
82
+ const result = ancestorIds
83
+ .slice()
84
+ .reverse()
85
+ .reduce<{ stopped: boolean; ids: Array<Identifier> }>(
86
+ (acc, ancestorId) => {
87
+ if (acc.stopped) {
88
+ return acc
89
+ }
90
+
91
+ const ancestor = findItemById(items, ancestorId)
92
+ if (!ancestor) {
93
+ return acc
94
+ }
95
+
96
+ const descendantIds = getAllDescendantIds(ancestor)
97
+ const allDescendantsChecked = descendantIds.every(
98
+ (id) => newCheckedIds.includes(id) || acc.ids.includes(id),
99
+ )
100
+
101
+ if (allDescendantsChecked) {
102
+ return { stopped: false, ids: [...acc.ids, ancestorId] }
103
+ }
104
+ // If this ancestor isn't fully checked, outer ancestors won't be either
105
+ return { stopped: true, ids: acc.ids }
106
+ },
107
+ { stopped: false, ids: [] },
108
+ )
109
+
110
+ return result.ids
111
+ }
112
+
113
+ const getDescendantCheckState = (item: TableNode) => {
114
+ const originalItem = findItemById(items, item.id)
115
+ const descendantIds = originalItem ? getAllDescendantIds(originalItem) : []
116
+ const checkedCount = descendantIds.filter((id) => checkedRows.includes(id)).length
117
+
118
+ return {
119
+ hasChildren: descendantIds.length > 0,
120
+ allChecked: descendantIds.length > 0 && checkedCount === descendantIds.length,
121
+ someChecked: checkedCount > 0 && checkedCount < descendantIds.length,
122
+ descendantIds,
123
+ }
124
+ }
125
+
126
+ const isChecked = (item: TableNode): boolean => {
127
+ const { allChecked } = getDescendantCheckState(item)
128
+ // If all descendants are checked, treat parent as checked too
129
+ return checkedRows.includes(item.id) || allChecked
130
+ }
131
+
132
+ const isIndeterminate = (item: TableNode): boolean => {
133
+ const { hasChildren, someChecked } = getDescendantCheckState(item)
134
+ // Indeterminate only when some (but not all/none) descendants are checked
135
+ return hasChildren && !isChecked(item) && someChecked
136
+ }
137
+
138
+ const handleCheck = (item: TableNode, checked: boolean, event?: CheckboxChangeEvent) => {
139
+ const shiftPressed = event?.shiftKey
140
+
141
+ // Handle shift+click range selection
142
+ if (shiftPressed && lastCheckedId !== null && checked) {
143
+ const flatList = flattenItems(items)
144
+ const lastIndex = flatList.findIndex((n) => n.id === lastCheckedId)
145
+ const currentIndex = flatList.findIndex((n) => n.id === item.id)
146
+
147
+ if (lastIndex !== -1 && currentIndex !== -1) {
148
+ const start = Math.min(lastIndex, currentIndex)
149
+ const end = Math.max(lastIndex, currentIndex)
150
+ const itemsInRange = flatList.slice(start, end + 1)
151
+
152
+ setCheckedRows((current) => {
153
+ return itemsInRange.reduce(
154
+ (newCheckedIds, rangeItem) => {
155
+ const { descendantIds } = getDescendantCheckState(rangeItem)
156
+ const allIds = [rangeItem.id, ...descendantIds]
157
+ const idsToAdd = allIds.filter((id) => !newCheckedIds.includes(id))
158
+ const withDescendants = [...newCheckedIds, ...idsToAdd]
159
+
160
+ // Check if any ancestors should also be auto-checked
161
+ const ancestorsToCheck = getAncestorsToAutoCheck(
162
+ items,
163
+ rangeItem.id,
164
+ withDescendants,
165
+ ).filter((id) => !withDescendants.includes(id))
166
+ return [...withDescendants, ...ancestorsToCheck]
167
+ },
168
+ [...current],
169
+ )
170
+ })
171
+
172
+ setLastCheckedId(item.id)
173
+ return
174
+ }
175
+ }
176
+
177
+ const { descendantIds } = getDescendantCheckState(item)
178
+ const allIds = [item.id, ...descendantIds]
179
+ const ancestorIds = getAllAncestorIds(items, item.id)
180
+ setCheckedRows((current) => {
181
+ if (checked) {
182
+ // Add this item and descendants (filter duplicates)
183
+ const idsToAdd = allIds.filter((id) => !current.includes(id))
184
+ const newCheckedIds = [...current, ...idsToAdd]
185
+ // Check if any ancestors should also be auto-checked
186
+ const ancestorsToCheck = getAncestorsToAutoCheck(items, item.id, newCheckedIds).filter(
187
+ (id) => !newCheckedIds.includes(id),
188
+ )
189
+ return [...newCheckedIds, ...ancestorsToCheck]
190
+ }
191
+ // When unchecking, also uncheck all ancestors
192
+ const idsToRemove = [...allIds, ...ancestorIds]
193
+ return current.filter((id) => !idsToRemove.includes(id))
194
+ })
195
+
196
+ setLastCheckedId(item.id)
197
+ }
198
+
199
+ return { isChecked, isIndeterminate, handleCheck, checkedItems: checkedRows }
200
+ }
@@ -0,0 +1,117 @@
1
+ import { Children, ReactElement, ReactNode, isValidElement, useEffect, useState } from 'react'
2
+ import { useTable } from './table-context'
3
+
4
+ interface ColumnProps {
5
+ width?: string | number
6
+ resize?: boolean
7
+ }
8
+
9
+ interface HeaderProps {
10
+ children?: ReactNode
11
+ }
12
+
13
+ export const useColumnLayout = (header: ReactElement<HeaderProps>, id?: string) => {
14
+ const { select, multiselect } = useTable()
15
+ const hasSelectionColumn = select || multiselect
16
+ const columns = Children.toArray(header.props.children).filter(
17
+ (child): child is ReactElement<ColumnProps> => isValidElement(child),
18
+ )
19
+
20
+ const formatWidth = (width: string | number | undefined): string => {
21
+ if (width === undefined) {
22
+ return 'minmax(0px, 1fr)'
23
+ }
24
+
25
+ if (typeof width === 'number') {
26
+ return `${width}px`
27
+ }
28
+
29
+ return width
30
+ }
31
+
32
+ const initialWidths: string = `${hasSelectionColumn ? '8px' : ''} ${columns
33
+ .map((child) => {
34
+ const width = child.props.width
35
+ return formatWidth(width)
36
+ })
37
+ .join(' ')}`
38
+
39
+ const [columnLayout, setColumnLayout] = useState<string>(initialWidths)
40
+
41
+ // Restore layout from localStorage when columns are available
42
+ useEffect(() => {
43
+ // Don't try to restore layout if columns haven't loaded yet
44
+ // (columns are often loaded asynchronously)
45
+ if (columns.length === 0) {
46
+ return
47
+ }
48
+
49
+ // If no id, just use the default widths from props
50
+ if (!id) {
51
+ setColumnLayout(initialWidths)
52
+ return
53
+ }
54
+
55
+ const storageKey = `table-${id}`
56
+ const storedLayout = localStorage.getItem(storageKey)
57
+
58
+ // No stored layout - use the default widths from props
59
+ if (!storedLayout) {
60
+ setColumnLayout(initialWidths)
61
+ return
62
+ }
63
+
64
+ const storedColumns = storedLayout.match(/(?:minmax\([^()]*\)|[^\s]+)/g)
65
+
66
+ // Column count mismatch - clear storage and use default widths
67
+ if (!storedColumns || storedColumns.length !== columns.length + (hasSelectionColumn ? 1 : 0)) {
68
+ localStorage.removeItem(storageKey)
69
+ setColumnLayout(initialWidths)
70
+ return
71
+ }
72
+
73
+ // Restore from localStorage
74
+ setColumnLayout(storedLayout)
75
+ }, [columns.length, hasSelectionColumn, id, initialWidths])
76
+
77
+ // On layout change, save to local storage.
78
+ const handleLayoutChange = (newLayout: string) => {
79
+ if (!id) {
80
+ return
81
+ }
82
+
83
+ const storageKey = `table-${id}`
84
+
85
+ const newLayoutArray = newLayout.match(/(?:minmax\([^()]*\)|[^\s]+)/g)
86
+ if (!newLayoutArray) {
87
+ return
88
+ }
89
+
90
+ if (hasSelectionColumn) {
91
+ newLayoutArray.shift()
92
+ }
93
+
94
+ let newLayoutString = newLayoutArray
95
+ .map((width, index) => {
96
+ const column = columns[index]
97
+ const columnWidth = column?.props?.width
98
+ const resizeable = column?.props?.resize !== false
99
+
100
+ return resizeable || !columnWidth
101
+ ? width.includes('minmax')
102
+ ? width
103
+ : `minmax(${width}, 1fr)`
104
+ : formatWidth(columnWidth)
105
+ })
106
+ .join(' ')
107
+
108
+ if (hasSelectionColumn) {
109
+ newLayoutString = `8px ${newLayoutString}`
110
+ }
111
+
112
+ localStorage.setItem(storageKey, newLayoutString)
113
+ setColumnLayout(newLayoutString)
114
+ }
115
+
116
+ return { handleLayoutChange, columnLayout }
117
+ }
@@ -0,0 +1,134 @@
1
+ import { isNumber } from 'lodash-es'
2
+ import { ReactNode, RefObject, useCallback, useMemo, useState } from 'react'
3
+ import { createGroupEmptyStateMarker } from './is-group-empty-state'
4
+ import { getRowHeight } from './table'
5
+ import { useTable } from './table-context'
6
+
7
+ export const useGroups = (
8
+ data: Array<any>,
9
+ tableRef: RefObject<any>,
10
+ grouped?: {
11
+ counts: Array<number>
12
+ header: (index: number) => ReactNode
13
+ emptyState?: (index: number) => ReactNode
14
+ },
15
+ ) => {
16
+ const { collapsedGroups, density } = useTable()
17
+ const rowHeight = getRowHeight(density)
18
+ const [firstIndex, setFirstIndex] = useState(0)
19
+
20
+ if (grouped) {
21
+ if (!Array.isArray(grouped.counts)) {
22
+ throw new Error('Grouped counts must be an array.')
23
+ }
24
+
25
+ const totalGroupedItems = grouped.counts.reduce((prev, current) => prev + current, 0)
26
+ if (totalGroupedItems !== data?.length) {
27
+ throw new Error('All items must be assigned to a group.')
28
+ }
29
+ }
30
+
31
+ const groupedData = useMemo(() => {
32
+ if (!grouped) {
33
+ return data || []
34
+ }
35
+
36
+ let offset = 0
37
+ const groups = grouped.counts.map((count, i) => {
38
+ const isCollapsed = collapsedGroups?.includes(i)
39
+ const rows = !isCollapsed ? data.slice(offset, offset + count) : []
40
+
41
+ const group = [grouped.header(i), ...rows]
42
+
43
+ if (count === 0 && !isCollapsed && grouped.emptyState) {
44
+ group.push(createGroupEmptyStateMarker(), createGroupEmptyStateMarker(true))
45
+ }
46
+
47
+ offset += count
48
+ return group
49
+ })
50
+ return groups.flat()
51
+ }, [data, grouped, collapsedGroups])
52
+
53
+ const groupIndexByItemIndex = useMemo(() => {
54
+ if (!grouped) {
55
+ return []
56
+ }
57
+
58
+ const items: Array<number> = []
59
+ grouped.counts.forEach((count, i) => {
60
+ if (!collapsedGroups?.includes(i)) {
61
+ const hasEmptyState = count === 0 && grouped.emptyState
62
+ for (let j = 0; j < count + 1 + (hasEmptyState ? 2 : 0); j++) {
63
+ items.push(i)
64
+ }
65
+ } else {
66
+ items.push(i)
67
+ }
68
+ })
69
+
70
+ return items
71
+ }, [grouped, collapsedGroups])
72
+
73
+ const getGroupIndex = useCallback(
74
+ (rowIndex: number): number => {
75
+ return groupIndexByItemIndex[rowIndex] ?? 0
76
+ },
77
+ [groupIndexByItemIndex],
78
+ )
79
+
80
+ const currentGroupIndex = useMemo(() => {
81
+ if (!grouped) {
82
+ return undefined
83
+ }
84
+ return getGroupIndex(firstIndex)
85
+ }, [grouped, firstIndex, getGroupIndex])
86
+
87
+ const currentGroup = useMemo(() => {
88
+ if (!grouped || grouped.counts.length === 0) {
89
+ return null
90
+ }
91
+ const groupIndex = getGroupIndex(firstIndex)
92
+ return grouped.header(groupIndex)
93
+ }, [grouped, firstIndex, getGroupIndex])
94
+
95
+ const handleScroll = useCallback(() => {
96
+ if (!grouped || !tableRef.current) {
97
+ return
98
+ }
99
+ const scrollTop: number = Number(
100
+ tableRef.current.querySelector('[data-table-library_table] > div > div')?.scrollTop,
101
+ )
102
+
103
+ if (scrollTop >= 0 && scrollTop <= rowHeight) {
104
+ setFirstIndex(0)
105
+ } else {
106
+ try {
107
+ groupedData.reduce((prev, _, currentIndex) => {
108
+ const height = rowHeight
109
+ const start = isNumber(prev) ? prev : rowHeight
110
+ const end = start + height
111
+
112
+ if (scrollTop >= start && scrollTop <= end) {
113
+ setFirstIndex(currentIndex)
114
+ throw 'BREAK'
115
+ }
116
+
117
+ return end
118
+ })
119
+ } catch (e) {
120
+ if (e !== 'BREAK') {
121
+ throw e
122
+ }
123
+ }
124
+ }
125
+ }, [grouped, tableRef, groupedData, rowHeight])
126
+
127
+ return {
128
+ data: groupedData,
129
+ handleScroll,
130
+ currentGroup,
131
+ getGroupIndex,
132
+ currentGroupIndex,
133
+ }
134
+ }