@@ -8,7 +8,7 @@ import {IconChevron} from 'sentry/icons';
8
8
import { t } from 'sentry/locale' ;
9
9
import { space } from 'sentry/styles/space' ;
10
10
11
- export interface FoldSectionProps {
11
+ interface FoldSectionStatelessProps {
12
12
children : React . ReactNode ;
13
13
sectionKey : string ;
14
14
/**
@@ -21,13 +21,26 @@ export interface FoldSectionProps {
21
21
actions ?: React . ReactNode ;
22
22
className ?: string ;
23
23
/**
24
- * Should this section be initially open, gets overridden by user preferences
24
+ * If this is defined on the initial render, then the component will be
25
+ * treated as a controlled component, meaning the parent component will need
26
+ * to handle this component's state. You will likely need to use `onChange`
27
+ * callback.
25
28
*/
26
- initialCollapse ?: boolean ;
29
+ isCollapsed ?: boolean ;
27
30
/**
28
31
* Additional margin required to ensure that scrolling to `sectionKey` is correct.
29
32
*/
30
33
navScrollMargin ?: number ;
34
+ /**
35
+ * Callback when the collapsed state changes
36
+ */
37
+ onChange ?: ( collapsed : boolean ) => void ;
38
+ /**
39
+ * Callback when scrolled to a collapsed section. If this is a controlled
40
+ * component, you'll likely want to update state to have "is collapsed" =
41
+ * false so that the section is expanded when you scroll to it.
42
+ */
43
+ onScrollToCollapsedSection ?: ( element ?: HTMLElement ) => void ;
31
44
/**
32
45
* Disable the ability for the user to collapse the section
33
46
*/
@@ -36,24 +49,83 @@ export interface FoldSectionProps {
36
49
style ?: CSSProperties ;
37
50
}
38
51
52
+ interface FoldSectionProps extends FoldSectionStatelessProps {
53
+ /**
54
+ * Should this section be initially open, gets overridden by user preferences
55
+ */
56
+ initialCollapse ?: boolean ;
57
+ }
58
+
59
+ export function FoldSection ( {
60
+ isCollapsed : isCollapsedProp ,
61
+ onChange,
62
+ onScrollToCollapsedSection,
63
+ initialCollapse = false ,
64
+ ...props
65
+ } : FoldSectionProps ) {
66
+ //
67
+ const isControlled = useRef ( isCollapsedProp !== undefined ) ;
68
+ const [ isCollapsedState , setIsCollapsed ] = useState ( initialCollapse ) ;
69
+
70
+ const handleScrollToCollapsedSection = useCallback (
71
+ ( element : HTMLElement | undefined ) => {
72
+ if ( isControlled . current ) {
73
+ onScrollToCollapsedSection ?.( element ) ;
74
+ } else {
75
+ setIsCollapsed ( false ) ;
76
+ }
77
+ } ,
78
+ [ onScrollToCollapsedSection , setIsCollapsed ]
79
+ ) ;
80
+
81
+ const handleChange = useCallback (
82
+ ( nextCollapsed : boolean ) => {
83
+ if ( isControlled . current ) {
84
+ if ( typeof onChange !== 'function' ) {
85
+ // eslint-disable-next-line no-console
86
+ console . warn (
87
+ new Error (
88
+ 'Controlled prop `isCollapsed` used without on `onChange` prop. You likely need an `onChange` so parent component can handle the collapsed state.'
89
+ )
90
+ ) ;
91
+ }
92
+ onChange ?.( nextCollapsed ) ;
93
+ } else {
94
+ setIsCollapsed ( nextCollapsed ) ;
95
+ }
96
+ } ,
97
+ [ onChange , setIsCollapsed ]
98
+ ) ;
99
+
100
+ return (
101
+ < FoldSectionStateless
102
+ { ...props }
103
+ isCollapsed = { isCollapsedProp ?? isCollapsedState }
104
+ onChange = { handleChange }
105
+ onScrollToCollapsedSection = { handleScrollToCollapsedSection }
106
+ />
107
+ ) ;
108
+ }
109
+
39
110
/**
40
111
* This is a near-duplicate of the component in the
41
112
* steamlined issues view, without localStorage syncing and
42
113
* analytics.
43
114
*/
44
- export function FoldSection ( {
115
+ function FoldSectionStateless ( {
45
116
ref,
46
117
children,
47
118
title,
48
119
sectionKey,
49
120
actions,
50
121
className,
122
+ isCollapsed,
123
+ onChange,
124
+ onScrollToCollapsedSection,
51
125
navScrollMargin = 0 ,
52
- initialCollapse = false ,
53
126
preventCollapse = false ,
54
- } : FoldSectionProps ) {
127
+ } : FoldSectionStatelessProps ) {
55
128
const hasAttemptedScroll = useRef ( false ) ;
56
- const [ isCollapsed , setIsCollapsed ] = useState ( initialCollapse ) ;
57
129
58
130
const scrollToSection = useCallback (
59
131
( element : HTMLElement | null ) => {
@@ -68,15 +140,15 @@ export function FoldSection({
68
140
const [ , hash ] = window . location . hash . split ( '#' ) ;
69
141
if ( hash === sectionKey ) {
70
142
if ( isCollapsed ) {
71
- setIsCollapsed ( false ) ;
143
+ onScrollToCollapsedSection ?. ( element ) ;
72
144
}
73
145
74
146
// Delay scrollIntoView to allow for layout changes to take place
75
147
setTimeout ( ( ) => element ?. scrollIntoView ( ) , 100 ) ;
76
148
}
77
149
}
78
150
} ,
79
- [ sectionKey , navScrollMargin , isCollapsed , setIsCollapsed ]
151
+ [ sectionKey , navScrollMargin , isCollapsed , onScrollToCollapsedSection ]
80
152
) ;
81
153
82
154
// This controls disabling the InteractionStateLayer when hovering over action items. We don't
@@ -87,10 +159,9 @@ export function FoldSection({
87
159
( e : React . MouseEvent ) => {
88
160
e . preventDefault ( ) ; // Prevent browser summary/details behaviour
89
161
window . getSelection ( ) ?. removeAllRanges ( ) ; // Prevent text selection on expand
90
- setIsCollapsed ( ! isCollapsed ) ;
91
- // onToggleCollapse
162
+ onChange ?.( ! isCollapsed ) ;
92
163
} ,
93
- [ isCollapsed , setIsCollapsed ]
164
+ [ isCollapsed , onChange ]
94
165
) ;
95
166
const labelPrefix = isCollapsed ? t ( 'View' ) : t ( 'Collapse' ) ;
96
167
const labelSuffix = typeof title === 'string' ? title + t ( ' Section' ) : t ( 'Section' ) ;
0 commit comments