Skip to content

Commit 897784f

Browse files
committed
fix(ui-modal): make Modal's header non-sticky with small window height
Closes: INSTUI-4391
1 parent 47f6eb8 commit 897784f

File tree

5 files changed

+94
-10
lines changed

5 files changed

+94
-10
lines changed

packages/ui-modal/src/Modal/ModalHeader/props.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type ModalHeaderOwnProps = {
3636
children?: React.ReactNode
3737
variant?: 'default' | 'inverse'
3838
spacing?: 'default' | 'compact'
39+
smallPortView?: boolean
3940
}
4041

4142
type ModalHeaderStyleProps = {
@@ -55,7 +56,8 @@ type ModalHeaderStyle = ComponentStyle<'modalHeader'>
5556
const propTypes: PropValidators<PropKeys> = {
5657
children: PropTypes.node,
5758
variant: PropTypes.oneOf(['default', 'inverse']),
58-
spacing: PropTypes.oneOf(['default', 'compact'])
59+
spacing: PropTypes.oneOf(['default', 'compact']),
60+
smallPortView: PropTypes.bool
5961
}
6062

6163
const allowedProps: AllowedPropKeys = ['children', 'variant', 'spacing']

packages/ui-modal/src/Modal/ModalHeader/styles.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const generateStyle = (
4444
props: ModalHeaderProps,
4545
state: ModalHeaderStyleProps
4646
): ModalHeaderStyle => {
47-
const { variant, spacing } = props
47+
const { variant, spacing, smallPortView } = props
4848
const { withCloseButton } = state
4949

5050
const sizeVariants = {
@@ -83,6 +83,7 @@ const generateStyle = (
8383
borderBottomWidth: '0.0625rem',
8484
borderBottomStyle: 'solid',
8585
borderBottomColor: componentTheme.borderColor,
86+
...(smallPortView ? { position: 'relative' } : {}),
8687
...sizeVariants[spacing!],
8788
...inverseStyle
8889
}

packages/ui-modal/src/Modal/index.tsx

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class Modal extends Component<ModalProps, ModalState> {
8686

8787
this.state = {
8888
transitioning: false,
89-
open: props.open ?? false
89+
open: props.open ?? false,
90+
windowHeight: window.innerHeight
9091
}
9192
}
9293

@@ -101,6 +102,7 @@ class Modal extends Component<ModalProps, ModalState> {
101102

102103
componentDidMount() {
103104
this.props.makeStyles?.()
105+
window.addEventListener('resize', this.updateHeight)
104106
}
105107

106108
componentDidUpdate(prevProps: ModalProps) {
@@ -110,6 +112,14 @@ class Modal extends Component<ModalProps, ModalState> {
110112
this.props.makeStyles?.()
111113
}
112114

115+
componentWillUnmount() {
116+
window.removeEventListener('resize', this.updateHeight)
117+
}
118+
119+
updateHeight = () => {
120+
this.setState({ windowHeight: window.innerHeight })
121+
}
122+
113123
get defaultFocusElement() {
114124
return this.props.defaultFocusElement
115125
}
@@ -146,21 +156,85 @@ class Modal extends Component<ModalProps, ModalState> {
146156
}
147157
}
148158

159+
getWindowHeightInRem = (): number => {
160+
if (typeof window === 'undefined' || typeof document === 'undefined') {
161+
// Default to a large number to avoid SSR issues
162+
return Infinity
163+
}
164+
const rootFontSize = parseFloat(
165+
getComputedStyle(document.documentElement)?.fontSize || '16'
166+
)
167+
if (isNaN(rootFontSize) || rootFontSize <= 0) {
168+
return Infinity
169+
}
170+
return window.innerHeight / rootFontSize
171+
}
172+
149173
renderChildren() {
150174
const { children, variant, overflow } = this.props
151175

176+
// header should be non-sticky for small viewport height (ca. 320px)
177+
if (this.getWindowHeightInRem() <= 20) {
178+
return this.renderForSmallViewportHeight()
179+
}
180+
152181
return Children.map(children as ReactElement, (child) => {
153182
if (!child) return // ignore null, falsy children
183+
return this.cloneChildWithProps(child, variant, overflow)
184+
})
185+
}
186+
187+
renderForSmallViewportHeight() {
188+
const { children, variant, overflow, styles } = this.props
154189

190+
const headerAndBody: React.ReactNode[] = []
191+
192+
const childrenArray = Children.toArray(children)
193+
194+
// Separate header and body elements
195+
const filteredChildren = childrenArray.filter((child) => {
155196
if (isValidElement(child)) {
156-
return safeCloneElement(child, {
157-
variant: variant,
158-
overflow: (child?.props as { overflow: string })?.overflow || overflow
159-
})
160-
} else {
161-
return child
197+
if (child.type === Modal.Header || child.type === Modal.Body) {
198+
if (child.type === Modal.Header) {
199+
const headerWithProp = safeCloneElement(child, {
200+
smallPortView: true
201+
})
202+
headerAndBody.push(headerWithProp)
203+
} else {
204+
headerAndBody.push(child)
205+
}
206+
return false
207+
}
162208
}
209+
return true
163210
})
211+
212+
// Adds the <div> to the beginning of the filteredChildren array
213+
if (headerAndBody.length > 0) {
214+
filteredChildren.unshift(
215+
<div css={styles?.joinedHeaderAndBody}>{headerAndBody}</div>
216+
)
217+
}
218+
219+
return Children.map(filteredChildren as ReactElement[], (child) => {
220+
if (!child) return // ignore null, falsy children
221+
return this.cloneChildWithProps(child, variant, overflow)
222+
})
223+
}
224+
225+
cloneChildWithProps(
226+
child: React.ReactNode,
227+
variant: string | undefined,
228+
overflow: string | undefined
229+
) {
230+
if (isValidElement(child)) {
231+
return safeCloneElement(child, {
232+
variant: variant,
233+
overflow: (child?.props as { overflow: string })?.overflow || overflow
234+
})
235+
} else {
236+
return child
237+
}
164238
}
165239

166240
renderDialog(

packages/ui-modal/src/Modal/props.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,14 @@ type ModalProps = ModalOwnProps &
189189
WithStyleProps<ModalTheme, ModalStyle> &
190190
OtherHTMLAttributes<ModalOwnProps>
191191

192-
type ModalStyle = ComponentStyle<'modal' | 'constrainContext'>
192+
type ModalStyle = ComponentStyle<
193+
'modal' | 'constrainContext' | 'joinedHeaderAndBody'
194+
>
193195

194196
type ModalState = {
195197
transitioning: boolean
196198
open: boolean
199+
windowHeight: number
197200
}
198201

199202
const propTypes: PropValidators<PropKeys> = {

packages/ui-modal/src/Modal/styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ const generateStyle = (
108108
position: 'relative',
109109
width: '100%',
110110
height: '100%'
111+
},
112+
joinedHeaderAndBody: {
113+
borderRadius: componentTheme.borderRadius,
114+
overflowY: 'scroll'
111115
}
112116
}
113117
}

0 commit comments

Comments
 (0)