@idealyst/datagrid 1.0.40 → 1.0.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/datagrid",
3
- "version": "1.0.40",
3
+ "version": "1.0.44",
4
4
  "description": "High-performance datagrid component for React and React Native",
5
5
  "documentation": "https://github.com/your-username/idealyst-framework/tree/main/packages/datagrid#readme",
6
6
  "main": "src/index.ts",
@@ -35,8 +35,8 @@
35
35
  "publish:npm": "npm publish"
36
36
  },
37
37
  "peerDependencies": {
38
- "@idealyst/components": "^1.0.40",
39
- "@idealyst/theme": "^1.0.40",
38
+ "@idealyst/components": "^1.0.44",
39
+ "@idealyst/theme": "^1.0.44",
40
40
  "react": ">=16.8.0",
41
41
  "react-native": ">=0.60.0",
42
42
  "react-native-unistyles": "^3.0.4",
@@ -0,0 +1,267 @@
1
+ import React, { useState, useCallback, useMemo, memo } from 'react';
2
+ import { FlatList, FlatListProps, TouchableOpacity, ScrollView } from 'react-native';
3
+ import { View, Text } from '@idealyst/components';
4
+ import type { DataGridProps, Column } from './types';
5
+
6
+ export function DataGrid<T extends Record<string, any>>({
7
+ data,
8
+ columns,
9
+ rowHeight = 48,
10
+ headerHeight = 56,
11
+ onRowClick,
12
+ onSort,
13
+ virtualized = true,
14
+ height = 400,
15
+ width = '100%',
16
+ style,
17
+ headerStyle,
18
+ rowStyle,
19
+ selectedRows = [],
20
+ onSelectionChange,
21
+ multiSelect = false,
22
+ stickyHeader = true,
23
+ }: DataGridProps<T>) {
24
+ const [sortColumn, setSortColumn] = useState<string | null>(null);
25
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
26
+
27
+ // Calculate column widths and total width
28
+ const { columnWidths, totalWidth } = useMemo(() => {
29
+ const widths = columns.map(col => col.width || 120);
30
+ const total = widths.reduce((sum, width) => sum + (typeof width === 'number' ? width : 120), 0);
31
+ return { columnWidths: widths, totalWidth: total };
32
+ }, [columns]);
33
+
34
+ const handleSort = useCallback((column: Column<T>) => {
35
+ if (!column.sortable) return;
36
+
37
+ const newDirection = sortColumn === column.key && sortDirection === 'asc' ? 'desc' : 'asc';
38
+ setSortColumn(column.key);
39
+ setSortDirection(newDirection);
40
+ onSort?.(column, newDirection);
41
+ }, [sortColumn, sortDirection, onSort]);
42
+
43
+ const handleRowPress = useCallback((item: T, index: number) => {
44
+ if (onSelectionChange) {
45
+ let newSelection: number[];
46
+ if (multiSelect) {
47
+ if (selectedRows.includes(index)) {
48
+ newSelection = selectedRows.filter(i => i !== index);
49
+ } else {
50
+ newSelection = [...selectedRows, index];
51
+ }
52
+ } else {
53
+ newSelection = selectedRows.includes(index) ? [] : [index];
54
+ }
55
+ onSelectionChange(newSelection);
56
+ }
57
+ onRowClick?.(item, index);
58
+ }, [selectedRows, onSelectionChange, multiSelect, onRowClick]);
59
+
60
+ const renderHeader = useMemo(() => (
61
+ <View style={[
62
+ {
63
+ flexDirection: 'row',
64
+ backgroundColor: '#ffffff',
65
+ borderBottomWidth: 2,
66
+ borderBottomColor: '#ddd',
67
+ height: headerHeight,
68
+ width: totalWidth,
69
+ elevation: stickyHeader ? 4 : 0,
70
+ shadowColor: '#000',
71
+ shadowOffset: { width: 0, height: 2 },
72
+ shadowOpacity: 0.1,
73
+ shadowRadius: 4,
74
+ },
75
+ headerStyle
76
+ ]}>
77
+ {columns.map((column, colIndex) => {
78
+ const width = columnWidths[colIndex];
79
+ const isLastColumn = colIndex === columns.length - 1;
80
+
81
+ return (
82
+ <View
83
+ key={column.key}
84
+ style={[
85
+ {
86
+ width: typeof width === 'number' ? width : 120,
87
+ height: '100%',
88
+ paddingHorizontal: 12,
89
+ paddingVertical: 8,
90
+ borderRightWidth: isLastColumn ? 0 : 1,
91
+ borderRightColor: '#ddd',
92
+ justifyContent: 'center',
93
+ backgroundColor: '#ffffff',
94
+ },
95
+ column.headerStyle
96
+ ]}
97
+ >
98
+ <Text
99
+ weight="bold"
100
+ size="small"
101
+ onPress={column.sortable ? () => handleSort(column) : undefined}
102
+ style={{
103
+ color: '#374151',
104
+ ...(column.sortable ? { textDecorationLine: 'underline' } : {})
105
+ }}
106
+ >
107
+ {column.header}
108
+ {column.sortable && sortColumn === column.key && (
109
+ <Text style={{ fontSize: 10, color: '#6366f1' }}>
110
+ {sortDirection === 'asc' ? ' ▲' : ' ▼'}
111
+ </Text>
112
+ )}
113
+ </Text>
114
+ </View>
115
+ );
116
+ })}
117
+ </View>
118
+ ), [columns, columnWidths, headerHeight, stickyHeader, headerStyle, sortColumn, sortDirection, handleSort]);
119
+
120
+ // Memoized row component to prevent unnecessary re-renders
121
+ const MemoizedRow = memo<{ item: T; index: number; isSelected: boolean; onPress: () => void }>(
122
+ ({ item, index, isSelected, onPress }) => {
123
+ const computedRowStyle = typeof rowStyle === 'function' ? rowStyle(item, index) : rowStyle;
124
+
125
+ return (
126
+ <TouchableOpacity
127
+ onPress={onPress}
128
+ style={[
129
+ {
130
+ flexDirection: 'row',
131
+ backgroundColor: isSelected ? '#e0e7ff' : (index % 2 === 0 ? '#fafafa' : '#ffffff'),
132
+ borderBottomWidth: 1,
133
+ borderBottomColor: '#e5e7eb',
134
+ height: rowHeight,
135
+ width: totalWidth,
136
+ },
137
+ computedRowStyle
138
+ ]}
139
+ >
140
+ {columns.map((column, colIndex) => {
141
+ const value = column.accessor ? column.accessor(item) : item[column.key];
142
+ const cellContent = column.render ? column.render(value, item, index) : value;
143
+ const computedCellStyle = typeof column.cellStyle === 'function'
144
+ ? column.cellStyle(value, item)
145
+ : column.cellStyle;
146
+ const width = columnWidths[colIndex];
147
+ const isLastColumn = colIndex === columns.length - 1;
148
+
149
+ return (
150
+ <View
151
+ key={column.key}
152
+ style={[
153
+ {
154
+ width: typeof width === 'number' ? width : 120,
155
+ height: '100%',
156
+ paddingHorizontal: 12,
157
+ paddingVertical: 8,
158
+ borderRightWidth: isLastColumn ? 0 : 1,
159
+ borderRightColor: '#e5e7eb',
160
+ justifyContent: 'center',
161
+ },
162
+ computedCellStyle
163
+ ]}
164
+ >
165
+ {typeof cellContent === 'string' || typeof cellContent === 'number' ? (
166
+ <Text size="small" numberOfLines={1}>
167
+ {cellContent}
168
+ </Text>
169
+ ) : (
170
+ cellContent
171
+ )}
172
+ </View>
173
+ );
174
+ })}
175
+ </TouchableOpacity>
176
+ );
177
+ },
178
+ // Custom comparison function to prevent unnecessary re-renders
179
+ (prevProps, nextProps) => {
180
+ // Only re-render if the item, selection state, or index changes
181
+ return (
182
+ prevProps.item === nextProps.item &&
183
+ prevProps.isSelected === nextProps.isSelected &&
184
+ prevProps.index === nextProps.index
185
+ );
186
+ }
187
+ );
188
+
189
+ const renderRow = useCallback(({ item, index }: { item: T; index: number }) => {
190
+ const isSelected = selectedRows.includes(index);
191
+
192
+ return (
193
+ <MemoizedRow
194
+ item={item}
195
+ index={index}
196
+ isSelected={isSelected}
197
+ onPress={() => handleRowPress(item, index)}
198
+ />
199
+ );
200
+ }, [selectedRows, handleRowPress]);
201
+
202
+ // Memoize the key extractor
203
+ const keyExtractor = useCallback((item: T, index: number) => {
204
+ // Use a unique key based on item data if available, otherwise use index
205
+ return item.id ? `row-${item.id}` : `row-${index}`;
206
+ }, []);
207
+
208
+ const flatListProps: Partial<FlatListProps<T>> = {
209
+ data,
210
+ renderItem: renderRow,
211
+ keyExtractor,
212
+ getItemLayout: virtualized ? (data, index) => ({
213
+ length: rowHeight,
214
+ offset: rowHeight * index,
215
+ index,
216
+ }) : undefined,
217
+ removeClippedSubviews: virtualized,
218
+ maxToRenderPerBatch: virtualized ? 15 : data.length,
219
+ windowSize: virtualized ? 15 : 21,
220
+ initialNumToRender: virtualized ? 10 : data.length,
221
+ updateCellsBatchingPeriod: 100,
222
+ showsVerticalScrollIndicator: true,
223
+ showsHorizontalScrollIndicator: true,
224
+ horizontal: false,
225
+ style: {
226
+ flex: 1,
227
+ backgroundColor: '#ffffff',
228
+ },
229
+ ListHeaderComponent: stickyHeader ? renderHeader : undefined,
230
+ stickyHeaderIndices: stickyHeader ? [0] : undefined,
231
+ };
232
+
233
+ return (
234
+ <View style={[
235
+ {
236
+ backgroundColor: '#ffffff',
237
+ borderWidth: 1,
238
+ borderColor: '#d1d5db',
239
+ borderRadius: 8,
240
+ overflow: 'hidden',
241
+ width,
242
+ height,
243
+ },
244
+ style
245
+ ]}>
246
+ <ScrollView
247
+ horizontal
248
+ showsHorizontalScrollIndicator={true}
249
+ style={{ flex: 1 }}
250
+ contentContainerStyle={{ minWidth: totalWidth }}
251
+ >
252
+ <View style={{ width: totalWidth, flex: 1 }}>
253
+ {!stickyHeader && renderHeader()}
254
+ <FlatList
255
+ {...flatListProps}
256
+ style={{ flex: 1 }}
257
+ showsVerticalScrollIndicator={true}
258
+ ListHeaderComponent={stickyHeader ? renderHeader : undefined}
259
+ stickyHeaderIndices={stickyHeader ? [0] : undefined}
260
+ scrollEnabled={true}
261
+ nestedScrollEnabled={true}
262
+ />
263
+ </View>
264
+ </ScrollView>
265
+ </View>
266
+ );
267
+ }
@@ -1,8 +1,7 @@
1
- import React, { useState, useMemo, useCallback } from 'react';
2
- import { View, Text, Button } from '@idealyst/components';
3
- import { createStyleSheet, useStyles } from 'react-native-unistyles';
1
+ import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
2
+ import { View, Text } from '@idealyst/components';
4
3
  import { ScrollView } from '../primitives/ScrollView';
5
- import { VirtualizedList } from '../primitives/VirtualizedList';
4
+ import { Table, TableRow, TableCell, TableHeader, TableBody } from '../primitives/Table';
6
5
  import type { DataGridProps, Column } from './types';
7
6
 
8
7
  export function DataGrid<T extends Record<string, any>>({
@@ -23,9 +22,107 @@ export function DataGrid<T extends Record<string, any>>({
23
22
  multiSelect = false,
24
23
  stickyHeader = true,
25
24
  }: DataGridProps<T>) {
26
- const { styles } = useStyles(stylesheet);
27
25
  const [sortColumn, setSortColumn] = useState<string | null>(null);
28
26
  const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
27
+ const [scrollTop, setScrollTop] = useState(0);
28
+ const scrollRef = useRef<any>(null);
29
+
30
+ // Virtualization calculations
31
+ const visibleRange = useMemo(() => {
32
+ if (!virtualized || typeof height !== 'number') {
33
+ return { start: 0, end: data.length - 1, offsetY: 0 };
34
+ }
35
+
36
+ const containerHeight = height - headerHeight;
37
+ const startIndex = Math.floor(scrollTop / rowHeight);
38
+ const visibleCount = Math.ceil(containerHeight / rowHeight) + 2; // Add buffer
39
+ const endIndex = Math.min(startIndex + visibleCount, data.length - 1);
40
+ const offsetY = startIndex * rowHeight;
41
+
42
+ return { start: startIndex, end: endIndex, offsetY };
43
+ }, [scrollTop, height, headerHeight, rowHeight, data.length, virtualized]);
44
+
45
+ const totalHeight = useMemo(() => {
46
+ return virtualized ? data.length * rowHeight : 'auto';
47
+ }, [data.length, rowHeight, virtualized]);
48
+
49
+ const visibleData = useMemo(() => {
50
+ if (!virtualized) return data;
51
+ return data.slice(visibleRange.start, visibleRange.end + 1);
52
+ }, [data, visibleRange.start, visibleRange.end, virtualized]);
53
+
54
+ // Calculate minimum table width for horizontal scrolling
55
+ const minTableWidth = useMemo(() => {
56
+ return columns.reduce((total, column) => {
57
+ return total + (column.width ? (typeof column.width === 'number' ? column.width : 120) : 120);
58
+ }, 0);
59
+ }, [columns]);
60
+
61
+ const handleScroll = useCallback((e: any) => {
62
+ if (virtualized) {
63
+ // Handle both web and React Native scroll events
64
+ const scrollY = e.currentTarget?.scrollTop ?? e.nativeEvent?.contentOffset?.y ?? 0;
65
+ setScrollTop(scrollY);
66
+ }
67
+ }, [virtualized]);
68
+
69
+ // Helper function to get consistent column styles
70
+ const getColumnStyle = (column: Column<T>) => {
71
+ const baseStyle = {
72
+ boxSizing: 'border-box' as const,
73
+ flexShrink: 0,
74
+ };
75
+
76
+ if (column.width) {
77
+ return {
78
+ ...baseStyle,
79
+ width: column.width,
80
+ flexGrow: 0,
81
+ flexBasis: column.width,
82
+ };
83
+ } else {
84
+ return {
85
+ ...baseStyle,
86
+ flexGrow: 1,
87
+ flexBasis: 0,
88
+ minWidth: column.minWidth || 120,
89
+ ...(column.maxWidth ? { maxWidth: column.maxWidth } : {}),
90
+ };
91
+ }
92
+ };
93
+
94
+ // Helper function for consistent cell base styles
95
+ const getCellBaseStyle = (theme: any) => ({
96
+ padding: theme.spacing.sm,
97
+ borderRightWidth: 1,
98
+ });
99
+
100
+ // Helper function for platform-specific header styles
101
+ const getStickyHeaderStyle = (theme: any) => {
102
+ if (!stickyHeader) return {};
103
+
104
+ // Platform detection - check if we're on web or native
105
+ const isWeb = typeof document !== 'undefined';
106
+
107
+ if (isWeb) {
108
+ return {
109
+ position: 'sticky' as const,
110
+ top: 0,
111
+ zIndex: 100,
112
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
113
+ };
114
+ } else {
115
+ // React Native - use elevation instead of boxShadow
116
+ return {
117
+ elevation: 4,
118
+ zIndex: 100,
119
+ shadowColor: '#000',
120
+ shadowOffset: { width: 0, height: 2 },
121
+ shadowOpacity: 0.1,
122
+ shadowRadius: 4,
123
+ };
124
+ }
125
+ };
29
126
 
30
127
  const handleSort = useCallback((column: Column<T>) => {
31
128
  if (!column.sortable) return;
@@ -54,143 +151,154 @@ export function DataGrid<T extends Record<string, any>>({
54
151
  }, [selectedRows, onSelectionChange, multiSelect, onRowClick]);
55
152
 
56
153
  const renderHeader = () => (
57
- <View style={[styles.header, headerStyle]}>
154
+ <TableRow style={(theme) => ({
155
+ backgroundColor: stickyHeader ? '#ffffff' : theme.colors.neutral[50],
156
+ borderBottomWidth: 2,
157
+ borderBottomColor: theme.colors.neutral[200],
158
+ minHeight: headerHeight,
159
+ flexDirection: 'row',
160
+ ...getStickyHeaderStyle(theme),
161
+ ...headerStyle,
162
+ })}>
58
163
  {columns.map((column) => (
59
- <View
164
+ <TableCell
60
165
  key={column.key}
61
- style={[
62
- styles.headerCell,
63
- { width: column.width || 'auto', minWidth: column.minWidth, maxWidth: column.maxWidth },
64
- column.headerStyle,
65
- ]}
166
+ width={column.width}
167
+ style={(theme) => ({
168
+ ...getCellBaseStyle(theme),
169
+ borderRightColor: theme.colors.neutral[200],
170
+ backgroundColor: stickyHeader ? '#ffffff' : theme.colors.neutral[50],
171
+ ...column.headerStyle,
172
+ })}
66
173
  >
67
- <Text weight="bold" style={styles.headerText}>
174
+ <Text weight="bold" style={(theme) => ({
175
+ fontSize: 14,
176
+ color: theme.colors.neutral[700],
177
+ })}>
68
178
  {column.header}
179
+ {column.sortable && (
180
+ <Text as="span" style={(theme) => ({
181
+ fontSize: 10,
182
+ marginLeft: theme.spacing.xs,
183
+ color: theme.colors.primary[500],
184
+ })}>
185
+ {sortColumn === column.key ? ` ${sortDirection === 'asc' ? '▲' : '▼'}` : ''}
186
+ </Text>
187
+ )}
69
188
  </Text>
70
- {column.sortable && (
71
- <Text style={styles.sortIndicator}>
72
- {sortColumn === column.key ? (sortDirection === 'asc' ? '▲' : '▼') : ''}
73
- </Text>
74
- )}
75
- </View>
189
+ </TableCell>
76
190
  ))}
77
- </View>
191
+ </TableRow>
78
192
  );
79
193
 
80
- const renderRow = ({ item, index }: { item: T; index: number }) => {
81
- const isSelected = selectedRows.includes(index);
82
- const computedRowStyle = typeof rowStyle === 'function' ? rowStyle(item, index) : rowStyle;
194
+ const renderRow = (item: T, virtualIndex: number) => {
195
+ const actualIndex = virtualized ? visibleRange.start + virtualIndex : virtualIndex;
196
+ const isSelected = selectedRows.includes(actualIndex);
197
+ const computedRowStyle = typeof rowStyle === 'function' ? rowStyle(item, actualIndex) : rowStyle;
83
198
 
84
199
  return (
85
- <View
86
- style={[
87
- styles.row,
88
- isSelected && styles.selectedRow,
89
- computedRowStyle,
90
- ]}
91
- onPress={() => handleRowClick(item, index)}
200
+ <TableRow
201
+ key={actualIndex}
202
+ style={(theme) => ({
203
+ borderBottomWidth: 1,
204
+ borderBottomColor: theme.colors.neutral[100],
205
+ backgroundColor: isSelected ? theme.colors.primary[50] : theme.colors.background,
206
+ minHeight: rowHeight,
207
+ flexDirection: 'row',
208
+ ...computedRowStyle,
209
+ })}
210
+ onPress={() => handleRowClick(item, actualIndex)}
92
211
  >
93
212
  {columns.map((column) => {
94
213
  const value = column.accessor ? column.accessor(item) : item[column.key];
95
- const cellContent = column.render ? column.render(value, item, index) : value;
214
+ const cellContent = column.render ? column.render(value, item, actualIndex) : value;
96
215
  const computedCellStyle = typeof column.cellStyle === 'function'
97
216
  ? column.cellStyle(value, item)
98
217
  : column.cellStyle;
99
218
 
100
219
  return (
101
- <View
220
+ <TableCell
102
221
  key={column.key}
103
- style={[
104
- styles.cell,
105
- { width: column.width || 'auto', minWidth: column.minWidth, maxWidth: column.maxWidth },
106
- computedCellStyle,
107
- ]}
222
+ width={column.width}
223
+ style={(theme) => ({
224
+ ...getCellBaseStyle(theme),
225
+ borderRightColor: theme.colors.neutral[100],
226
+ ...computedCellStyle,
227
+ })}
108
228
  >
109
229
  {typeof cellContent === 'string' || typeof cellContent === 'number' ? (
110
230
  <Text>{cellContent}</Text>
111
231
  ) : (
112
232
  cellContent
113
233
  )}
114
- </View>
234
+ </TableCell>
115
235
  );
116
236
  })}
117
- </View>
237
+ </TableRow>
118
238
  );
119
239
  };
120
240
 
121
241
  const containerHeight = typeof height === 'number' ? height : undefined;
122
242
 
123
243
  return (
124
- <View style={[styles.container, { width, height }, style]}>
125
- {stickyHeader && renderHeader()}
126
- {virtualized && containerHeight ? (
127
- <VirtualizedList
128
- data={data}
129
- renderItem={renderRow}
130
- itemHeight={rowHeight}
131
- height={containerHeight - (stickyHeader ? headerHeight : 0)}
132
- width={width}
133
- />
134
- ) : (
135
- <ScrollView
136
- style={styles.scrollView}
137
- showsVerticalScrollIndicator={true}
138
- >
139
- {!stickyHeader && renderHeader()}
140
- {data.map((item, index) => renderRow({ item, index }))}
141
- </ScrollView>
142
- )}
244
+ <View style={(theme) => ({
245
+ backgroundColor: theme.colors.background,
246
+ borderWidth: 1,
247
+ borderColor: theme.colors.neutral[200],
248
+ borderRadius: theme.radius.md,
249
+ overflow: 'hidden',
250
+ width,
251
+ height,
252
+ display: 'flex',
253
+ flexDirection: 'column',
254
+ ...style,
255
+ })}>
256
+ <ScrollView
257
+ style={{
258
+ flex: 1,
259
+ ...(containerHeight ? { maxHeight: containerHeight } : {})
260
+ }}
261
+ contentContainerStyle={{
262
+ width: minTableWidth,
263
+ }}
264
+ showsVerticalScrollIndicator={true}
265
+ showsHorizontalScrollIndicator={true}
266
+ bounces={false}
267
+ directionalLockEnabled={false}
268
+ onScroll={handleScroll}
269
+ scrollEventThrottle={16}
270
+ >
271
+ <Table style={{
272
+ width: minTableWidth,
273
+ ...(virtualized ? { height: totalHeight } : {})
274
+ }}>
275
+ <TableHeader>
276
+ {renderHeader()}
277
+ </TableHeader>
278
+ <TableBody>
279
+ {virtualized && visibleRange.offsetY > 0 && (
280
+ <TableRow style={{ height: visibleRange.offsetY }}>
281
+ <TableCell
282
+ style={{ padding: 0, borderWidth: 0, height: visibleRange.offsetY }}
283
+ colSpan={columns.length}
284
+ >
285
+ </TableCell>
286
+ </TableRow>
287
+ )}
288
+ {visibleData.map((item, index) => renderRow(item, index))}
289
+ {virtualized && (data.length - visibleRange.end - 1) > 0 && (
290
+ <TableRow style={{ height: (data.length - visibleRange.end - 1) * rowHeight }}>
291
+ <TableCell
292
+ style={{ padding: 0, borderWidth: 0, height: (data.length - visibleRange.end - 1) * rowHeight }}
293
+ colSpan={columns.length}
294
+ >
295
+ </TableCell>
296
+ </TableRow>
297
+ )}
298
+ </TableBody>
299
+ </Table>
300
+ </ScrollView>
143
301
  </View>
144
302
  );
145
303
  }
146
304
 
147
- const stylesheet = createStyleSheet((theme) => ({
148
- container: {
149
- backgroundColor: theme.colors.background,
150
- borderWidth: 1,
151
- borderColor: theme.colors.neutral[200],
152
- borderRadius: theme.radius.md,
153
- overflow: 'hidden',
154
- },
155
- header: {
156
- flexDirection: 'row',
157
- backgroundColor: theme.colors.neutral[50],
158
- borderBottomWidth: 2,
159
- borderBottomColor: theme.colors.neutral[200],
160
- },
161
- headerCell: {
162
- flexDirection: 'row',
163
- alignItems: 'center',
164
- justifyContent: 'space-between',
165
- padding: theme.spacing.sm,
166
- borderRightWidth: 1,
167
- borderRightColor: theme.colors.neutral[200],
168
- },
169
- headerText: {
170
- fontSize: 14,
171
- color: theme.colors.neutral[700],
172
- },
173
- sortIndicator: {
174
- fontSize: 10,
175
- marginLeft: theme.spacing.xs,
176
- color: theme.colors.primary[500],
177
- },
178
- row: {
179
- flexDirection: 'row',
180
- borderBottomWidth: 1,
181
- borderBottomColor: theme.colors.neutral[100],
182
- backgroundColor: theme.colors.background,
183
- },
184
- selectedRow: {
185
- backgroundColor: theme.colors.primary[50],
186
- },
187
- cell: {
188
- padding: theme.spacing.sm,
189
- borderRightWidth: 1,
190
- borderRightColor: theme.colors.neutral[100],
191
- justifyContent: 'center',
192
- },
193
- scrollView: {
194
- flex: 1,
195
- },
196
- }));