-
Notifications
You must be signed in to change notification settings - Fork 49k
[DevTools] Added resize support for Components panel. #18046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
6f5d8e5
65bbec5
16dcd67
1c6add2
23fba3a
0236754
1f2e54a
9cee4ef
fc02184
0c2ad23
3ba1909
554f7e8
c243f40
7f4dbd9
c773be3
27f5906
267f242
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
.ComponentsWrapper { | ||
position: relative; | ||
width: 100%; | ||
height: 100%; | ||
display: flex; | ||
flex-direction: row; | ||
background-color: var(--color-background); | ||
color: var(--color-text); | ||
font-family: var(--font-family-sans); | ||
} | ||
|
||
@media screen and (max-width: 600px) { | ||
.ComponentsWrapper { | ||
flex-direction: column; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import type {ElementRef} from 'react'; | ||
|
||
import * as React from 'react'; | ||
import {useEffect, useLayoutEffect, useReducer, useRef} from 'react'; | ||
import { | ||
localStorageGetItem, | ||
localStorageSetItem, | ||
} from 'react-devtools-shared/src/storage'; | ||
import styles from './ComponentsResizer.css'; | ||
|
||
const LOCAL_STORAGE_KEY = 'React::DevTools::createResizeReducer'; | ||
const VERTICAL_MODE_MAX_WIDTH = 600; | ||
const MINIMUM_SIZE = 50; | ||
|
||
type Props = {| | ||
children: ({ | ||
resizeElementRef: ElementRef<HTMLElement>, | ||
onResizeStart: () => void, | ||
}) => React$Node, | ||
|}; | ||
|
||
export default function ComponentsResizer({children}: Props) { | ||
const wrapperElementRef = useRef(null); | ||
const resizeElementRef = useRef(null); | ||
const [state, dispatch] = createResizeReducer( | ||
wrapperElementRef, | ||
resizeElementRef, | ||
); | ||
|
||
const {isResizing} = state; | ||
|
||
const onResizeStart = () => | ||
dispatch({type: 'ACTION_SET_IS_RESIZING', payload: true}); | ||
const onResizeEnd = () => | ||
dispatch({type: 'ACTION_SET_IS_RESIZING', payload: false}); | ||
|
||
const onResize = event => { | ||
const resizeElement = resizeElementRef.current; | ||
const wrapperElement = wrapperElementRef.current; | ||
|
||
if (!isResizing || wrapperElement === null || resizeElement === null) { | ||
return; | ||
} | ||
|
||
event.preventDefault(); | ||
|
||
const orientation = getOrientation(wrapperElementRef); | ||
|
||
const {height, width, left, top} = wrapperElement.getBoundingClientRect(); | ||
|
||
const currentMousePosition = | ||
orientation === 'horizontal' ? event.clientX - left : event.clientY - top; | ||
|
||
const boundaryMin = MINIMUM_SIZE; | ||
const boundaryMax = | ||
orientation === 'horizontal' | ||
? width - MINIMUM_SIZE | ||
: height - MINIMUM_SIZE; | ||
|
||
const isMousePositionInBounds = | ||
currentMousePosition > boundaryMin && currentMousePosition < boundaryMax; | ||
|
||
if (isMousePositionInBounds) { | ||
const resizedElementDimension = | ||
orientation === 'horizontal' ? width : height; | ||
const actionType = | ||
orientation === 'horizontal' | ||
? 'ACTION_SET_HORIZONTAL_PERCENTAGE' | ||
: 'ACTION_SET_VERTICAL_PERCENTAGE'; | ||
const percentage = (currentMousePosition / resizedElementDimension) * 100; | ||
|
||
resizeElement.style.setProperty( | ||
`--${orientation}-resize-percentage`, | ||
`${percentage}%`, | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are now setting css variables based on the orientation. Possible values: We use these two variables in CSS to determine the correct resize percentage based on the orientation using the media query that we have set up there. 🎊 I initially thought of using a ResizeObserver but it's far too complex, I think. 🙅♂ This was the cleanest solution that I could come up with. Thoughts, @bvaughn ? P.S.: |
||
|
||
dispatch({ | ||
type: actionType, | ||
payload: currentMousePosition / resizedElementDimension, | ||
}); | ||
} | ||
}; | ||
|
||
return ( | ||
<div | ||
ref={wrapperElementRef} | ||
className={styles.ComponentsWrapper} | ||
{...(isResizing && { | ||
onMouseMove: onResize, | ||
onMouseLeave: onResizeEnd, | ||
onMouseUp: onResizeEnd, | ||
})}> | ||
{children({resizeElementRef, onResizeStart})} | ||
</div> | ||
); | ||
} | ||
|
||
type Orientation = 'horizontal' | 'vertical'; | ||
|
||
type ResizeActionType = | ||
| 'ACTION_SET_DID_MOUNT' | ||
| 'ACTION_SET_IS_RESIZING' | ||
| 'ACTION_SET_HORIZONTAL_PERCENTAGE' | ||
| 'ACTION_SET_VERTICAL_PERCENTAGE'; | ||
|
||
type ResizeAction = {| | ||
type: ResizeActionType, | ||
payload: any, | ||
|}; | ||
|
||
type ResizeState = {| | ||
horizontalPercentage: number, | ||
isResizing: boolean, | ||
verticalPercentage: number, | ||
|}; | ||
|
||
function initResizeState(): ResizeState { | ||
let horizontalPercentage = 0.65; | ||
let verticalPercentage = 0.5; | ||
|
||
try { | ||
let data = localStorageGetItem(LOCAL_STORAGE_KEY); | ||
if (data != null) { | ||
data = JSON.parse(data); | ||
horizontalPercentage = data.horizontalPercentage; | ||
verticalPercentage = data.verticalPercentage; | ||
} | ||
} catch (error) {} | ||
|
||
return { | ||
horizontalPercentage, | ||
isResizing: false, | ||
verticalPercentage, | ||
}; | ||
} | ||
|
||
function resizeReducer(state: ResizeState, action: ResizeAction): ResizeState { | ||
switch (action.type) { | ||
case 'ACTION_SET_IS_RESIZING': | ||
return { | ||
...state, | ||
isResizing: action.payload, | ||
}; | ||
case 'ACTION_SET_HORIZONTAL_PERCENTAGE': | ||
return { | ||
...state, | ||
horizontalPercentage: action.payload, | ||
}; | ||
case 'ACTION_SET_VERTICAL_PERCENTAGE': | ||
return { | ||
...state, | ||
verticalPercentage: action.payload, | ||
}; | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
function getOrientation( | ||
wrapperElementRef: ElementRef<HTMLElement>, | ||
): null | Orientation { | ||
const wrapperElement = wrapperElementRef.current; | ||
if (wrapperElement != null) { | ||
const {width} = wrapperElement.getBoundingClientRect(); | ||
return width > VERTICAL_MODE_MAX_WIDTH ? 'horizontal' : 'vertical'; | ||
} | ||
return null; | ||
} | ||
|
||
function createResizeReducer(wrapperElementRef, resizeElementRef) { | ||
const [state, dispatch] = useReducer(resizeReducer, null, initResizeState); | ||
hristo-kanchev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const {horizontalPercentage, verticalPercentage} = state; | ||
const orientationRef = useRef(null); | ||
|
||
useLayoutEffect(() => { | ||
const orientation = getOrientation(wrapperElementRef); | ||
|
||
if (orientation !== orientationRef.current) { | ||
orientationRef.current = orientation; | ||
|
||
const percentage = | ||
orientation === 'horizontal' | ||
? horizontalPercentage | ||
: verticalPercentage; | ||
const resizeElement = resizeElementRef.current; | ||
|
||
resizeElement.style.setProperty( | ||
`--${orientation}-resize-percentage`, | ||
`${percentage * 100}%`, | ||
); | ||
hristo-kanchev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
}); | ||
|
||
useEffect(() => { | ||
const timeoutID = setTimeout(() => { | ||
localStorageSetItem( | ||
LOCAL_STORAGE_KEY, | ||
JSON.stringify({ | ||
horizontalPercentage, | ||
verticalPercentage, | ||
}), | ||
); | ||
}, 500); | ||
|
||
return () => clearTimeout(timeoutID); | ||
}, [horizontalPercentage, verticalPercentage]); | ||
|
||
return [state, dispatch]; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.