1
- // @ts -nocheck
2
-
3
1
import React from 'react'
4
2
5
3
import { styled } from './utils'
@@ -13,11 +11,15 @@ export const Entry = styled('div', {
13
11
} )
14
12
15
13
export const Label = styled ( 'span' , {
14
+ color : 'white' ,
15
+ } )
16
+
17
+ export const LabelButton = styled ( 'button' , {
16
18
cursor : 'pointer' ,
17
19
color : 'white' ,
18
20
} )
19
21
20
- export const Value = styled ( 'span' , ( props , theme ) => ( {
22
+ export const Value = styled ( 'span' , ( _props , theme ) => ( {
21
23
color : theme . danger ,
22
24
} ) )
23
25
@@ -32,7 +34,12 @@ export const Info = styled('span', {
32
34
fontSize : '.7em' ,
33
35
} )
34
36
35
- export const Expander = ( { expanded, style = { } , ...rest } ) => (
37
+ type ExpanderProps = {
38
+ expanded : boolean
39
+ style ?: React . CSSProperties
40
+ }
41
+
42
+ export const Expander = ( { expanded, style = { } } : ExpanderProps ) => (
36
43
< span
37
44
style = { {
38
45
display : 'inline-block' ,
@@ -45,43 +52,81 @@ export const Expander = ({ expanded, style = {}, ...rest }) => (
45
52
</ span >
46
53
)
47
54
48
- const DefaultRenderer = ( {
49
- handleEntry,
55
+ type Entry = {
56
+ label : string
57
+ }
58
+
59
+ type RendererProps = {
60
+ HandleEntry : HandleEntryComponent
61
+ label ?: string
62
+ value : unknown
63
+ subEntries : Entry [ ]
64
+ subEntryPages : Entry [ ] [ ]
65
+ type : string
66
+ expanded : boolean
67
+ toggleExpanded : ( ) => void
68
+ pageSize : number
69
+ }
70
+
71
+ /**
72
+ * Chunk elements in the array by size
73
+ *
74
+ * when the array cannot be chunked evenly by size, the last chunk will be
75
+ * filled with the remaining elements
76
+ *
77
+ * @example
78
+ * chunkArray(['a','b', 'c', 'd', 'e'], 2) // returns [['a','b'], ['c', 'd'], ['e']]
79
+ */
80
+ export function chunkArray < T > ( array : T [ ] , size : number ) : T [ ] [ ] {
81
+ if ( size < 1 ) return [ ]
82
+ let i = 0
83
+ const result : T [ ] [ ] = [ ]
84
+ while ( i < array . length ) {
85
+ result . push ( array . slice ( i , i + size ) )
86
+ i = i + size
87
+ }
88
+ return result
89
+ }
90
+
91
+ type Renderer = ( props : RendererProps ) => JSX . Element
92
+
93
+ export const DefaultRenderer : Renderer = ( {
94
+ HandleEntry,
50
95
label,
51
96
value,
52
- // path,
53
- subEntries,
54
- subEntryPages,
97
+ subEntries = [ ] ,
98
+ subEntryPages = [ ] ,
55
99
type,
56
- // depth,
57
- expanded,
58
- toggle,
100
+ expanded = false ,
101
+ toggleExpanded,
59
102
pageSize,
60
103
} ) => {
61
- const [ expandedPages , setExpandedPages ] = React . useState ( [ ] )
104
+ const [ expandedPages , setExpandedPages ] = React . useState < number [ ] > ( [ ] )
62
105
63
106
return (
64
107
< Entry key = { label } >
65
108
{ subEntryPages ?. length ? (
66
109
< >
67
- < Label onClick = { ( ) => toggle ( ) } >
110
+ < button onClick = { ( ) => toggleExpanded ( ) } >
68
111
< Expander expanded = { expanded } /> { label } { ' ' }
69
112
< Info >
70
113
{ String ( type ) . toLowerCase ( ) === 'iterable' ? '(Iterable) ' : '' }
71
114
{ subEntries . length } { subEntries . length > 1 ? `items` : `item` }
72
115
</ Info >
73
- </ Label >
116
+ </ button >
74
117
{ expanded ? (
75
118
subEntryPages . length === 1 ? (
76
119
< SubEntries >
77
- { subEntries . map ( entry => handleEntry ( entry ) ) }
120
+ { subEntries . map ( entry => (
121
+ < HandleEntry entry = { entry } />
122
+ ) ) }
78
123
</ SubEntries >
79
124
) : (
80
125
< SubEntries >
81
126
{ subEntryPages . map ( ( entries , index ) => (
82
127
< div key = { index } >
83
128
< Entry >
84
- < Label
129
+ < LabelButton
85
130
onClick = { ( ) =>
86
131
setExpandedPages ( old =>
87
132
old . includes ( index )
@@ -92,10 +137,12 @@ const DefaultRenderer = ({
92
137
>
93
138
< Expander expanded = { expanded } /> [{ index * pageSize } ...{ ' ' }
94
139
{ index * pageSize + pageSize - 1 } ]
95
- </ Label >
140
+ </ LabelButton >
96
141
{ expandedPages . includes ( index ) ? (
97
142
< SubEntries >
98
- { entries . map ( entry => handleEntry ( entry ) ) }
143
+ { entries . map ( entry => (
144
+ < HandleEntry entry = { entry } />
145
+ ) ) }
99
146
</ SubEntries >
100
147
) : null }
101
148
</ Entry >
@@ -117,36 +164,43 @@ const DefaultRenderer = ({
117
164
)
118
165
}
119
166
167
+ type HandleEntryComponent = ( props : { entry : Entry } ) => JSX . Element
168
+
169
+ type ExplorerProps = Partial < RendererProps > & {
170
+ renderer ?: Renderer
171
+ defaultExpanded ?: true | Record < string , boolean >
172
+ }
173
+
174
+ type Property = {
175
+ defaultExpanded ?: boolean | Record < string , boolean >
176
+ label : string
177
+ value : unknown
178
+ }
179
+
180
+ function isIterable ( x : any ) : x is Iterable < unknown > {
181
+ return Symbol . iterator in x
182
+ }
183
+
120
184
export default function Explorer ( {
121
185
value,
122
186
defaultExpanded,
123
187
renderer = DefaultRenderer ,
124
188
pageSize = 100 ,
125
- depth = 0 ,
126
189
...rest
127
- } ) {
128
- const [ expanded , setExpanded ] = React . useState ( defaultExpanded )
190
+ } : ExplorerProps ) {
191
+ const [ expanded , setExpanded ] = React . useState ( Boolean ( defaultExpanded ) )
192
+ const toggleExpanded = React . useCallback ( ( ) => setExpanded ( old => ! old ) , [ ] )
129
193
130
- const toggle = set => {
131
- setExpanded ( old => ( typeof set !== 'undefined' ? set : ! old ) )
132
- }
133
-
134
- const path = [ ]
194
+ let type : string = typeof value
195
+ let subEntries : Property [ ] = [ ]
135
196
136
- let type = typeof value
137
- let subEntries
138
- const subEntryPages = [ ]
139
-
140
- const makeProperty = sub => {
141
- const newPath = path . concat ( sub . label )
197
+ const makeProperty = ( sub : { label : string ; value : unknown } ) : Property => {
142
198
const subDefaultExpanded =
143
199
defaultExpanded === true
144
200
? { [ sub . label ] : true }
145
201
: defaultExpanded ?. [ sub . label ]
146
202
return {
147
203
...sub ,
148
- path : newPath ,
149
- depth : depth + 1 ,
150
204
defaultExpanded : subDefaultExpanded ,
151
205
}
152
206
}
@@ -155,54 +209,45 @@ export default function Explorer({
155
209
type = 'array'
156
210
subEntries = value . map ( ( d , i ) =>
157
211
makeProperty ( {
158
- label : i ,
212
+ label : i . toString ( ) ,
159
213
value : d ,
160
214
} )
161
215
)
162
216
} else if (
163
217
value !== null &&
164
218
typeof value === 'object' &&
219
+ isIterable ( value ) &&
165
220
typeof value [ Symbol . iterator ] === 'function'
166
221
) {
167
222
type = 'Iterable'
168
223
subEntries = Array . from ( value , ( val , i ) =>
169
224
makeProperty ( {
170
- label : i ,
225
+ label : i . toString ( ) ,
171
226
value : val ,
172
227
} )
173
228
)
174
229
} else if ( typeof value === 'object' && value !== null ) {
175
230
type = 'object'
176
- // eslint-disable-next-line no-shadow
177
- subEntries = Object . entries ( value ) . map ( ( [ label , value ] ) =>
231
+ subEntries = Object . entries ( value ) . map ( ( [ key , val ] ) =>
178
232
makeProperty ( {
179
- label,
180
- value,
233
+ label : key ,
234
+ value : val ,
181
235
} )
182
236
)
183
237
}
184
238
185
- if ( subEntries ) {
186
- let i = 0
187
-
188
- while ( i < subEntries . length ) {
189
- subEntryPages . push ( subEntries . slice ( i , i + pageSize ) )
190
- i = i + pageSize
191
- }
192
- }
239
+ const subEntryPages = chunkArray ( subEntries , pageSize )
193
240
194
241
return renderer ( {
195
- handleEntry : entry => (
196
- < Explorer key = { entry . label } renderer = { renderer } { ...rest } { ...entry } />
242
+ HandleEntry : ( { entry } ) => (
243
+ < Explorer value = { value } renderer = { renderer } { ...rest } { ...entry } />
197
244
) ,
198
245
type,
199
246
subEntries,
200
247
subEntryPages,
201
- depth,
202
248
value,
203
- path,
204
249
expanded,
205
- toggle ,
250
+ toggleExpanded ,
206
251
pageSize,
207
252
...rest ,
208
253
} )
0 commit comments