Created
August 5, 2023 04:52
-
-
Save littensy/9e99f07aac1c77b967ab093399b22bba to your computer and use it in GitHub Desktop.
🔥 Mock rem units in React-TS
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
import { map, useDebounceState, useViewport } from "@rbxts/pretty-react-hooks"; | |
import Roact, { createContext } from "@rbxts/roact"; | |
export interface RemProviderProps extends Roact.PropsWithChildren { | |
baseRem?: number; | |
remOverride?: number; | |
minimumRem?: number; | |
maximumRem?: number; | |
} | |
export const DEFAULT_REM = 16; | |
export const MIN_REM = 8; | |
const BASE_RESOLUTION = new Vector2(1920, 1020); | |
const MAX_ASPECT_RATIO = 19 / 9; | |
export const RemContext = createContext<number>(DEFAULT_REM); | |
export function RemProvider({ | |
baseRem = DEFAULT_REM, | |
minimumRem = MIN_REM, | |
maximumRem = math.huge, | |
remOverride, | |
children, | |
}: RemProviderProps) { | |
const [rem, setRem] = useDebounceState(DEFAULT_REM, { wait: 0.5, leading: true }); | |
useViewport((viewport: Vector2) => { | |
if (remOverride !== undefined) { | |
return remOverride; | |
} | |
// wide screens should not scale beyond iPhone aspect ratio | |
const resolution = new Vector2(math.min(viewport.X, viewport.Y * MAX_ASPECT_RATIO), viewport.Y); | |
const scale = resolution.Magnitude / BASE_RESOLUTION.Magnitude; | |
const desktop = resolution.X > resolution.Y || scale >= 1; | |
// portrait mode should downscale slower than landscape | |
const factor = desktop ? scale : map(scale, 0, 1, 0.25, 1); | |
setRem(math.clamp(math.round(baseRem * factor), minimumRem, maximumRem)); | |
}); | |
return <RemContext.Provider value={rem}>{children}</RemContext.Provider>; | |
} |
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
import { useCallback, useContext } from "@rbxts/roact"; | |
import { DEFAULT_REM, RemContext } from "../providers/rem-provider"; | |
export interface RemOptions { | |
minimum?: number; | |
maximum?: number; | |
} | |
interface RemCallback { | |
(value: number, mode?: RemScaleMode): number; | |
(value: UDim2, mode?: RemScaleMode): UDim2; | |
(value: UDim, mode?: RemScaleMode): UDim; | |
(value: Vector2, mode?: RemScaleMode): Vector2; | |
} | |
type RemScaleMode = "relative" | "unit"; | |
const scaleFunctions = { | |
number: (value: number, rem: number): number => { | |
return value * rem; | |
}, | |
UDim2: (value: UDim2, rem: number): UDim2 => { | |
return new UDim2(value.X.Scale, value.X.Offset * rem, value.Y.Scale, value.Y.Offset * rem); | |
}, | |
UDim: (value: UDim, rem: number): UDim => { | |
return new UDim(value.Scale, value.Offset * rem); | |
}, | |
Vector2: (value: Vector2, rem: number): Vector2 => { | |
return new Vector2(value.X * rem, value.Y * rem); | |
}, | |
}; | |
function useRemContext({ minimum = 0, maximum = math.huge }: RemOptions = {}) { | |
const rem = useContext(RemContext); | |
return math.clamp(rem, minimum, maximum); | |
} | |
export function useRem(options?: RemOptions): RemCallback { | |
const rem = useRemContext(options); | |
return useCallback( | |
<T>(value: T, mode: RemScaleMode = "unit"): T => { | |
const scale = scaleFunctions[typeOf(value) as never] as <T>(value: T, rem: number) => T; | |
if (!scale) { | |
return value; | |
} | |
return mode === "unit" ? scale(value, rem) : scale(value, rem / DEFAULT_REM); | |
}, | |
[rem], | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment