Skip to content
This repository was archived by the owner on Dec 13, 2023. It is now read-only.

ShopPage Component example #220

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/images/shop-page.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
494 changes: 494 additions & 0 deletions docs/tutorials/shop-page.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions examples/init.client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ local exampleData = {
name = "binding",
label = "Binding",
},
{
name = "shop-page",
label = "Shop Page",
},
}

for _, example in ipairs(exampleData) do
Expand Down
82 changes: 82 additions & 0 deletions examples/shop-page/Components/Item.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local Roact = require(ReplicatedStorage.Roact)

local Item = Roact.Component:extend("Item")

local PADDING = 20

function Item:init()
self.ref = Roact.createRef()
self.onMouseEnter = function()
self.toBigIcon:Play()
end
self.onMouseLeave = function()
self.toNormalIcon:Play()
end
self.onActivated = function()
local props = self.props

MarketplaceService:PromptProductPurchase(Players.LocalPlayer, props.productId)
end
end

function Item:render()
local props = self.props

local image = props.image
local price = props.price
local order = props.order or price

return Roact.createElement("ImageButton", {
BackgroundTransparency = 1,
Image = "",
LayoutOrder = order,
[Roact.Event.Activated] = self.onActivated,
[Roact.Event.MouseEnter] = self.onMouseEnter,
[Roact.Event.MouseLeave] = self.onMouseLeave,
}, {
Icon = Roact.createElement("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Image = image,
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(1, -PADDING, 1, -PADDING),
[Roact.Ref] = self.ref,
}),
PriceLabel = Roact.createElement("TextLabel", {
AnchorPoint = Vector2.new(0.5, 1),
BackgroundTransparency = 1,
Font = Enum.Font.SourceSans,
Text = ("R$ %d"):format(price),
TextColor3 = Color3.fromRGB(10, 200, 10),
TextScaled = true,
TextStrokeTransparency = 0,
TextStrokeColor3 = Color3.fromRGB(255, 255, 255),
Position = UDim2.new(0.5, 0, 1, 0),
Size = UDim2.new(1, 0, 0.3, 0),
}),
})
end

function Item:didMount()
local tweenInfo = TweenInfo.new(0.2)
local icon = self.ref:getValue()

self.toBigIcon = TweenService:Create(icon, tweenInfo, {
Size = UDim2.new(1, 0, 1, 0),
})
self.toNormalIcon = TweenService:Create(icon, tweenInfo, {
Size = UDim2.new(1, -PADDING, 1, -PADDING),
})
end

function Item:willUnmount()
self.toBigIcon:Destroy()
self.toNormalIcon:Destroy()
end

return Item
28 changes: 28 additions & 0 deletions examples/shop-page/Components/ItemList.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Components = script.Parent

local Roact = require(ReplicatedStorage.Roact)

local Item = require(Components:WaitForChild("Item"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need WaitForChild here? Is it unsafe for children of a folder to assume its siblings are available?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any guarantee that all the siblings are replicated. From my experience it's best to use WaitForChild since you're sure everything is there when you need it.

Maybe @LPGhatguy can validate this!


local function ItemList(props)
local items = props.items

local elements = {}

for i=1, #items do
local item = items[i]

elements[item.identifier] = Roact.createElement(Item, {
image = item.image,
price = item.price,
productId = item.productId,
order = item.order,
})
end

return Roact.createFragment(elements)
end

return ItemList
73 changes: 73 additions & 0 deletions examples/shop-page/Components/ShopPage.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Components = script.Parent

local Roact = require(ReplicatedStorage.Roact)
local ItemList = require(Components:WaitForChild("ItemList"))

local ShopPage = Roact.Component:extend("ShopPage")

ShopPage.defaultProps = {
padding = 0,
}

function ShopPage:init()
self:setState({
cellSize = 0,
canvasHeight = 0,
})

self.onAbsoluteSizeChanged = function(frame)
local props = self.props
local padding = props.padding
local itemsPerRow = props.itemsPerRow

local totalWidth = frame.AbsoluteSize.X
local cellWidth = (totalWidth - padding * (itemsPerRow + 1)) / itemsPerRow
local cellHeight = cellWidth / props.itemAspectRatio
local rows = math.ceil(#props.items / itemsPerRow)
local canvasHeight = rows * cellHeight + padding * (rows + 1)

self:setState({
cellSize = cellWidth,
canvasHeight = canvasHeight,
})
end
end

function ShopPage:render()
local props = self.props
local state = self.state

local items = props.items
local itemAspectRatio = props.itemAspectRatio
local frameProps = props.frameProps
local padding = props.padding

local cellSize = state.cellSize
local canvasHeight = state.canvasHeight

local newFrameProps = {}

for key, value in pairs(frameProps) do
newFrameProps[key] = value
end

newFrameProps.CanvasSize = UDim2.new(0, 0, 0, canvasHeight)
newFrameProps[Roact.Change.AbsoluteSize] = self.onAbsoluteSizeChanged

return Roact.createElement("ScrollingFrame", newFrameProps, {
UIGrid = Roact.createElement("UIGridLayout", {
CellPadding = UDim2.new(0, padding, 0, padding),
CellSize = UDim2.new(0, cellSize, 0, cellSize / itemAspectRatio),
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
Items = Roact.createElement(ItemList, {
items = items,
}),
})
end

return ShopPage
38 changes: 38 additions & 0 deletions examples/shop-page/Products.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
return {
{
identifier = "Red Visor",
image = "https://www.roblox.com/asset-thumbnail/image?assetId=139618072&width=420&height=420&format=png",
price = 30,
productId = 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use real productIds and use those as the stable keys for the list items so we can get rid of the identifier field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum I could put random values... Even if we keep the 0 values I could use the list index as a key and get rid of the identifier.

I think at first my plan was to show the name identifier in a label but I dropped that.

},
{
identifier = "Green Visor",
image = "https://www.roblox.com/asset-thumbnail/image?assetId=20264649&width=420&height=420&format=png",
price = 50,
productId = 0,
},
{
identifier = "Yellow Visor",
image = "https://www.roblox.com/asset-thumbnail/image?assetId=7135977&width=420&height=420&format=png",
price = 40,
productId = 0,
},
{
identifier = "Blue Visor",
image = "https://www.roblox.com/asset-thumbnail/image?assetId=1459035&width=420&height=420&format=png",
price = 55,
productId = 0,
},
{
identifier = "Dark Blue Visor",
image = "https://www.roblox.com/asset-thumbnail/image?assetId=68268372&width=420&height=420&format=png",
price = 60,
productId = 0,
},
{
identifier = "Purple Visor",
image = "https://www.roblox.com/asset-thumbnail/image?assetId=334661971&width=420&height=420&format=png",
price = 100,
productId = 0,
},
}
36 changes: 36 additions & 0 deletions examples/shop-page/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Roact = require(ReplicatedStorage.Roact)

local Products = require(script:WaitForChild("Products"))

local Components = script:WaitForChild("Components")
local ShopPage = require(Components:WaitForChild("ShopPage"))

return function()
local screenGui = Instance.new("ScreenGui")
screenGui.Parent = Players.LocalPlayer:WaitForChild("PlayerGui")

local shopPage = Roact.createElement(ShopPage, {
items = Products,
itemAspectRatio = 1,
itemsPerRow = 3,
padding = 10,
frameProps = {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundColor3 = Color3.fromRGB(0, 3, 20),
BorderSizePixel = 0,
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(0.5, 0, 0.5, 0),
},
})

local handle = Roact.mount(shopPage, screenGui)

local function stop()
Roact.unmount(handle)
end

return stop
end