forked from Uniswap/widgets
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: track widget width and add responsive styling (Uniswap#346)
* feat: track widget width throughout and add responsive styling * fix: ResizeObserver polyfill * fix: rename ResizeObserver
- Loading branch information
Showing
10 changed files
with
215 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { useIsWideWidget, useWidgetWidth } from 'hooks/useWidgetWidth' | ||
import { renderComponent } from 'test' | ||
|
||
import WidgetWrapper from './WidgetWrapper' | ||
|
||
const widgetWidthValueTestId = 'widgetWidthValue' | ||
const widgetWidthTypeTestId = 'widgetWidthType' | ||
const widgetIsWideTestId = 'widgetIsWide' | ||
|
||
function TestComponent() { | ||
const widgetWidth = useWidgetWidth() | ||
const isWide = useIsWideWidget() | ||
return ( | ||
<div> | ||
<div data-testid={widgetWidthValueTestId}>{widgetWidth}</div> | ||
<div data-testid={widgetIsWideTestId}>{isWide ? 'wide' : 'narrow'}</div> | ||
<div data-testid={widgetWidthTypeTestId}>{typeof widgetWidth}</div> | ||
</div> | ||
) | ||
} | ||
|
||
describe('WidgetWrapper', () => { | ||
it('should handle valid number width, narrow', () => { | ||
const component = renderComponent( | ||
<WidgetWrapper width={300}> | ||
<TestComponent /> | ||
</WidgetWrapper> | ||
) | ||
// 300 is the lowest width allowed | ||
expect(component.getByTestId(widgetWidthValueTestId).textContent).toBe('300') | ||
expect(component.getByTestId(widgetWidthTypeTestId).textContent).toBe('number') | ||
expect(component.getByTestId(widgetIsWideTestId).textContent).toBe('narrow') | ||
}) | ||
|
||
it('should handle valid number width, wide', () => { | ||
const component = renderComponent( | ||
<WidgetWrapper width={500}> | ||
<TestComponent /> | ||
</WidgetWrapper> | ||
) | ||
// 300 is the lowest width allowed | ||
expect(component.getByTestId(widgetWidthValueTestId).textContent).toBe('500') | ||
expect(component.getByTestId(widgetWidthTypeTestId).textContent).toBe('number') | ||
expect(component.getByTestId(widgetIsWideTestId).textContent).toBe('wide') | ||
}) | ||
|
||
it('should handle invalid number width', () => { | ||
const component = renderComponent( | ||
<WidgetWrapper width={200}> | ||
<TestComponent /> | ||
</WidgetWrapper> | ||
) | ||
// 300 is the lowest width allowed | ||
expect(component.getByTestId(widgetWidthValueTestId).textContent).toBe('300') | ||
expect(component.getByTestId(widgetWidthTypeTestId).textContent).toBe('number') | ||
expect(component.getByTestId(widgetIsWideTestId).textContent).toBe('narrow') | ||
}) | ||
|
||
it('should handle undefined width', () => { | ||
const component = renderComponent( | ||
<WidgetWrapper width={undefined}> | ||
<TestComponent /> | ||
</WidgetWrapper> | ||
) | ||
|
||
// We default to 360px if width is undefined | ||
expect(component.getByTestId(widgetWidthValueTestId).textContent).toBe('360') | ||
expect(component.getByTestId(widgetWidthTypeTestId).textContent).toBe('number') | ||
expect(component.getByTestId(widgetIsWideTestId).textContent).toBe('narrow') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { WidgetWidthProvider } from 'hooks/useWidgetWidth' | ||
import { PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react' | ||
import ResizeObserver from 'resize-observer-polyfill' | ||
import styled from 'styled-components/macro' | ||
import toLength from 'utils/toLength' | ||
|
||
const HORIZONTAL_PADDING = 8 | ||
|
||
const StyledWidgetWrapper = styled.div<{ width: number | string }>` | ||
-moz-osx-font-smoothing: grayscale; | ||
-webkit-font-smoothing: antialiased; | ||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); | ||
background-color: ${({ theme }) => theme.container}; | ||
border-radius: ${({ theme }) => theme.borderRadius}em; | ||
box-sizing: border-box; | ||
color: ${({ theme }) => theme.primary}; | ||
display: flex; | ||
flex-direction: column; | ||
font-size: 16px; | ||
font-smooth: always; | ||
font-variant: none; | ||
min-height: 360px; | ||
min-width: 300px; | ||
padding: ${HORIZONTAL_PADDING}px; | ||
position: relative; | ||
user-select: none; | ||
width: ${({ width }) => toLength(width)}; | ||
* { | ||
box-sizing: border-box; | ||
font-family: ${({ theme }) => (typeof theme.fontFamily === 'string' ? theme.fontFamily : theme.fontFamily.font)}; | ||
@supports (font-variation-settings: normal) { | ||
font-family: ${({ theme }) => (typeof theme.fontFamily === 'string' ? undefined : theme.fontFamily.variable)}; | ||
} | ||
} | ||
` | ||
|
||
interface WidgetWrapperProps { | ||
width: number | string | undefined | ||
className?: string | undefined | ||
} | ||
|
||
export default function WidgetWrapper(props: PropsWithChildren<WidgetWrapperProps>) { | ||
const initialWidth: string | number = useMemo(() => { | ||
if (props.width && props.width < 300) { | ||
console.warn(`Widget width must be at least 300px (you set it to ${props.width}). Falling back to 300px.`) | ||
return 300 | ||
} | ||
return props.width ?? 360 | ||
}, [props.width]) | ||
|
||
/** | ||
* We need to manually track the width of the widget because the width prop could be a string | ||
* like "100%" or "400px" instead of a number. | ||
*/ | ||
const ref = useRef<HTMLDivElement>(null) | ||
const [wrapperWidth, setWidgetWidth] = useState<number>( | ||
toLength(initialWidth) === initialWidth | ||
? 360 // If the initial width is a string, use default width until the ResizeObserver gives us the true width as a number. | ||
: (initialWidth as number) | ||
) | ||
useEffect(() => { | ||
const observer = new ResizeObserver((entries) => { | ||
// contentRect doesn't include padding or borders | ||
const { width } = entries[0].contentRect | ||
setWidgetWidth(width + 2 * HORIZONTAL_PADDING) | ||
}) | ||
const current = ref.current | ||
if (current) { | ||
observer.observe(ref.current) | ||
} | ||
return () => { | ||
if (current) { | ||
observer.unobserve(current) | ||
} | ||
} | ||
}, []) | ||
|
||
return ( | ||
<StyledWidgetWrapper width={initialWidth} className={props.className} ref={ref}> | ||
<WidgetWidthProvider width={wrapperWidth}>{props.children}</WidgetWidthProvider> | ||
</StyledWidgetWrapper> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { createContext, PropsWithChildren, useContext } from 'react' | ||
|
||
const WidgetWidthContext = createContext<number>(0) | ||
|
||
interface WidgetWidthProviderProps { | ||
width: number | ||
} | ||
|
||
export function WidgetWidthProvider({ width, children }: PropsWithChildren<WidgetWidthProviderProps>) { | ||
return <WidgetWidthContext.Provider value={width}>{children}</WidgetWidthContext.Provider> | ||
} | ||
|
||
export function useWidgetWidth(): number { | ||
return useContext(WidgetWidthContext) | ||
} | ||
|
||
export function useIsWideWidget(): boolean { | ||
const widgetWidth = useWidgetWidth() | ||
return widgetWidth > 420 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* Converts a number to a CSS length string. If the value is not a number, it is returned as is. | ||
* If the value is a number, we treat it like a pixel amount. | ||
* | ||
* @param length CSS length value, either a string like "100%" or "100px" or a number like 100 | ||
*/ | ||
export default function toLength(length: number | string): string { | ||
if (isNaN(Number(length))) { | ||
return length as string | ||
} else { | ||
return `${length}px` | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters