← Recipe: replay declarative motion

3. Replace src/main.luau

luastra/motion · stable UI identity · about 15 minutes
state changes once · host animates frames

The card keeps one ID while its changed translateX descriptor deliberately restarts that channel.

When to use it

Replace the complete entry module. Keep the card ID stable: a descriptor change restarts the affected channels without discarding semantic identity.

Focused example

--!strict

local Motion = require("luastra/motion")
local UI = require("luastra/ui")

local App = require("luastra/app")
local Root = {}
local run = 0
local fromLeft = true

local function entrance(): Motion.MotionMap
    local offset = if fromLeft then -28 else 28
    return {
        opacity = Motion.tween {
            from = 0,
            to = 1,
            durationMs = 180,
            easing = "easeOutCubic",
        },
        translateX = Motion.tween {
            from = offset,
            to = 0,
            durationMs = 260,
            easing = "easeOutCubic",
        },
    }
end

function Root.render(): UI.Node
    local side = if fromLeft then "left" else "right"
    local nextSide = if fromLeft then "right" else "left"
    return UI.Screen {
        id = "motion-recipe",
        width = "content",
        padding = "responsive",
        UI.Card {
            id = "motion/card",
            gap = "lg",
            padding = "lg",
            surface = "elevated",
            motion = entrance(),
            UI.Text { id = "motion/title", text = "Declarative entrance", variant = "title" },
            UI.Text {
                id = "motion/status",
                text = "Run " .. tostring(run) .. ": from " .. side,
                role = "status",
            },
            UI.Text {
                id = "motion/explanation",
                text = "Luau changes state once; the host animates the frames.",
                tone = "muted",
            },
            UI.Button {
                id = "motion/replay",
                text = "Replay from " .. nextSide,
                onTap = "motion.replay",
            },
        },
    }
end

function Root.handle(action: string, target: string, _value: string)
    if action == "motion.replay" and target == "motion/replay" then
        run += 1
        fromLeft = not fromLeft
    end
end

function Root.snapshot()
    return { run = run, fromLeft = fromLeft }
end

return App.compose {
    render = function(_outputs) return Root.render() end,
    handle = Root.handle,
    snapshot = Root.snapshot,
}

Complete inventory

View all pages in Recipe: replay declarative motion
← Previous: 2. Replace luastra.jsonNext: 4. Replace tests/smoke.luau →