Skip to content

Instantly share code, notes, and snippets.

@grilme99
Last active August 29, 2024 22:20
Show Gist options
  • Save grilme99/cb59582ff40f3dbc5b465694d2a7d39a to your computer and use it in GitHub Desktop.
Save grilme99/cb59582ff40f3dbc5b465694d2a7d39a to your computer and use it in GitHub Desktop.
React-lua hooks for Ripple
--[[
Returns whether the given value is a binding.
@param value The value to check.
@return Whether the value is a binding.
]]
local function isBinding(value: unknown): boolean
return ReactIs.isBinding(value)
end
--[[
Returns the value of a binding. If the given value is not a binding, it will
be returned as-is.
@param binding The binding to get the value of.
@return The value of the binding.
]]
local function getBindingValue<T>(value: T | Binding<T>): T
local value = value :: any
if isBinding(value) then
return value:getValue()
else
return value
end
end
local RunService = game:GetService("RunService")
local React = require("@pkg/react")
local Ripple = require("@pkg/ripple")
local useEventConnection = require("@src/hooks/use-event-connection")
local useMemo = React.useMemo
local useBinding = React.useBinding
--[[
Creates a memoized Motion object set to the given initial value.
Returns a binding that updates with the Motion, along with the Motion
object.
]]
local function useMotion<T>(initialValue: T)
local motion = useMemo(function()
return Ripple.createMotion(initialValue)
end, {})
local binding, setValue = useBinding(initialValue)
useEventConnection(RunService.Heartbeat, function(delta)
local value = motion:step(delta)
if value ~= binding:getValue() then
setValue(value)
end
end)
return binding, motion
end
return useMotion
local RunService = game:GetService("RunService")
local React = require("@pkg/react")
local Ripple = require("@pkg/ripple")
local BindingUtils = require("@src/utils/binding")
local getBindingValue = BindingUtils.getBindingValue
local useEventConnection = require("@src/hooks/use-event-connection")
local useMotion = require("@src/hooks/animation/use-motion")
local useRef = React.useRef
type Binding<T> = React.Binding<T>
type MotionGoal = Ripple.MotionGoal
export type SpringOptions = {
damping: number?,
frequency: number?,
mass: number?,
tension: number?,
friction: number?,
position: number?,
velocity: number?,
impulse: number?,
restingVelocity: number?,
restingPosition: number?,
}
type useSpringA = (goal: number | Binding<number>, options: SpringOptions?) -> Binding<number>
type useSpringB = <T>(goal: T | Binding<T>, options: SpringOptions?) -> Binding<T>
type useSpringC = (goal: MotionGoal | Binding<MotionGoal>, options: SpringOptions?) -> Binding<MotionGoal>
type useSpringFn = useSpringA & useSpringB & useSpringC
--[[
Applies spring animations to the given value, and updates the goal with the
latest value on every re-render. Returns a binding that updates with the
Motion.
### Example
```lua
local color = useSpring(props.color, config.spring.stiff)
```
]]
local function useSpring(goal: any, options: SpringOptions)
local binding, motion = useMotion(getBindingValue(goal))
local previousValue = useRef(getBindingValue(goal))
useEventConnection(RunService.Heartbeat, function()
local currentValue = getBindingValue(goal)
if currentValue ~= previousValue.current then
previousValue.current = currentValue
motion:spring(currentValue, options)
end
end)
return binding
end
return useSpring :: useSpringFn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment