Skip to content
This repository has been 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 2 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
32 changes: 32 additions & 0 deletions docs/tutorials/shop-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Shop page
_Goal: Create a page to sell developer products in a game._

[add an image of the result we are expecting]

## Top Component
Let's start by creating the top level component, that will be the page containing the items for sale in the game.

```lua

```

### Viewing the component


## Items Grid


## Product Item
[remind state and props]
[props validation]

### Adding some animations


## Integration

### In a Roact project
Here explain how the component can be used my another component or how it can be mounted on it's own (like main.client.lua does)

### In a non-Roact project
Explain how the page could be mounted in an existing UI instances tree. That opens the subject that it is possible to slowly port an existing UI to use Roact part by part.
82 changes: 82 additions & 0 deletions examples/shop-page/Components/ProductItem.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 Roact = require(ReplicatedStorage.Roact)

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

function ProductItem:init()
self:setState({
onMouseEnter = function()
-- grow the icon a little
end,
onMouseLeave = function()
-- go back to original size
end,
onActivated = function()
local props = self.props

MarketplaceService:PromptProductPurchase(Players.LocalPlayer, props.productId)
end
})
self.padding, self.updatePadding = Roact.createBinding(0)
end

function ProductItem:render()
local props = self.props
local state = self.state
local padding = self.padding

return Roact.createElement("ImageButton", {
BackgroundTransparency = 1,
Image = "",
LayoutOrder = props.order or props.price,
[Roact.Event.Activated] = state.onActivated,
[Roact.Event.MouseEnter] = state.onMouseEnter,
[Roact.Event.MouseLeave] = state.onMouseLeave,
}, {
Icon = Roact.createElement("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Image = props.image,
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(1, padding, 1, padding),
}),
PriceLabel = Roact.createElement("TextLabel", {
AnchorPoint = Vector2.new(0.5, 1),
BackgroundTransparency = 1,
Font = Enum.Font.SourceSans,
Text = ("R$ %d"):format(props.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 ProductItem.validateProps(props)
return pcall(function()
assert(
type(props.image) == "string",
("props.image should be a string (got %q)"):format(type(props.image))
)
assert(
type(props.price) == "number",
("props.price should be a number (got %q)"):format(type(props.price))
)
assert(
type(props.productId) == "string" or type(props.productId) == "number",
("props.productId should be a string or a number (got %q)"):format(type(props.productId))
)
assert(
props.order == nil or type(props.order) == "number",
("props.order should be a number (got %q)"):format(type(props.order))
)
end)
end
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved

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

local Components = ReplicatedStorage:WaitForChild("Components")

local Roact = require(ReplicatedStorage.Roact)

local ProductItem = require(Components:WaitForChild("ProductItem"))

local function ProductItemList(props)
local elements = {}

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

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

return Roact.createFragment(elements)
end

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

local Components = ReplicatedStorage:WaitForChild("Components")
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved

local Roact = require(ReplicatedStorage.Roact)

local ProductItemList = require(Components:WaitForChild("ProductItemList"))

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

ShopPage.defaultProps = {
padding = 0,
}

function ShopPage:init()
self:setState({
cellSize = 100,
canvasHeight = 100,
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved
})

self.onAbsoluteSizeChanged = function(frame)
local props = self.props
local state = self.state
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 padding = props.padding
local cellSize = state.cellSize
local canvasHeight = state.canvasHeight

local frameProps = {}

for key, value in pairs(props.frameProps) do
frameProps[key] = value
end
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved

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

return Roact.createElement("ScrollingFrame", frameProps, {
UIGrid = Roact.createElement("UIGridLayout", {
CellPadding = UDim2.new(0, padding, 0, padding),
CellSize = UDim2.new(0, cellSize, 0, cellSize * props.itemAspectRatio),
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
ProductItems = Roact.createElement(ProductItemList, {
items = props.items
}),
})
end

function ShopPage.validateProps(props)
return pcall(function()
assert(
type(props.padding) == "number",
("props.padding should be a number (got %q)"):format(type(props.padding))
)
assert(
type(props.frameProps) == "table",
("props.frameProps should be a table (got %q)"):format(type(props.frameProps))
)
end)
end

return ShopPage
21 changes: 21 additions & 0 deletions examples/shop-page/default.project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Roact Test Place",
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved
"tree": {
"$className": "DataModel",
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"main": {
"$path": "main.client.lua"
}
}
},
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Components": {
"$path": "Components"
}
}
}
}
67 changes: 67 additions & 0 deletions examples/shop-page/main.client.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Components = ReplicatedStorage:WaitForChild("Components")

local Roact = require(ReplicatedStorage.Roact)

Roact.setGlobalConfig({
propValidation = true
})

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

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

local shopPage = Roact.createElement(ShopPage, {
items = {
{
identifier = "Red Visor",
jeparlefrancais marked this conversation as resolved.
Show resolved Hide resolved
image = "https://www.roblox.com/asset-thumbnail/image?assetId=139618072&width=420&height=420&format=png",
price = 30,
productId = 0,
},
{
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,
},
},
itemAspectRatio = 1,
itemsPerRow = 3,
padding = 10,
frameProps = {
AnchorPoint = Vector2.new(0.5, 0.5),
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)