rojo 7.6.1

Enables professional-grade development tools for Roblox developers
Documentation
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages

local Roact = require(Packages.Roact)

local Settings = require(Plugin.Settings)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)

local Checkbox = require(Plugin.App.Components.Checkbox)
local Dropdown = require(Plugin.App.Components.Dropdown)
local IconButton = require(Plugin.App.Components.IconButton)
local Tag = require(Plugin.App.Components.Tag)

local e = Roact.createElement

local DIVIDER_FADE_SIZE = 0.1
local TAG_TYPES = {
	unstable = {
		text = "UNSTABLE",
		icon = Assets.Images.Icons.Warning,
		color = { "Settings", "Setting", "UnstableColor" },
	},
	debug = {
		text = "DEBUG",
		icon = Assets.Images.Icons.Debug,
		color = { "Settings", "Setting", "DebugColor" },
	},
}

local function getTextBoundsWithLineHeight(
	text: string,
	font: Font,
	textSize: number,
	width: number,
	lineHeight: number
)
	local textBounds = getTextBoundsAsync(text, font, textSize, width)

	local lineCount = math.ceil(textBounds.Y / textSize)
	local lineHeightAbsolute = textSize * lineHeight

	return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
end

local function getThemeColorFromPath(theme, path)
	local color = theme
	for _, key in path do
		if color[key] == nil then
			return theme.BrandColor
		end
		color = color[key]
	end
	return color
end

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

function Setting:init()
	self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
	self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
	self.inputSize, self.setInputSize = Roact.createBinding(Vector2.new(0, 0))

	self:setState({
		setting = Settings:get(self.props.id),
	})

	self.changedCleanup = Settings:onChanged(self.props.id, function(value)
		self:setState({
			setting = value,
		})
	end)
end

function Setting:willUnmount()
	self.changedCleanup()
end

function Setting:render()
	return Theme.with(function(theme)
		local settingsTheme = theme.Settings

		return e("Frame", {
			Size = self.contentSize:map(function(value)
				return UDim2.new(1, 0, 0, value.Y + 20)
			end),
			LayoutOrder = self.props.layoutOrder,
			ZIndex = -self.props.layoutOrder,
			BackgroundTransparency = 1,
			Visible = self.props.visible,

			[Roact.Change.AbsoluteSize] = function(object)
				self.setContainerSize(object.AbsoluteSize)
			end,
		}, {
			RightAligned = Roact.createElement("Frame", {
				BackgroundTransparency = 1,
				Size = UDim2.new(1, 0, 1, 0),
			}, {
				Layout = e("UIListLayout", {
					VerticalAlignment = Enum.VerticalAlignment.Center,
					HorizontalAlignment = Enum.HorizontalAlignment.Right,
					FillDirection = Enum.FillDirection.Horizontal,
					SortOrder = Enum.SortOrder.LayoutOrder,
					Padding = UDim.new(0, 2),
					[Roact.Change.AbsoluteContentSize] = function(rbx)
						self.setInputSize(rbx.AbsoluteContentSize)
					end,
				}),

				Input = if self.props.input ~= nil
					then self.props.input
					elseif self.props.options ~= nil then e(Dropdown, {
						locked = self.props.locked,
						lockedTooltip = self.props.lockedTooltip,
						options = self.props.options,
						active = self.state.setting,
						transparency = self.props.transparency,
						onClick = function(option)
							Settings:set(self.props.id, option)
						end,
					})
					else e(Checkbox, {
						locked = self.props.locked,
						lockedTooltip = self.props.lockedTooltip,
						active = self.state.setting,
						transparency = self.props.transparency,
						onClick = function()
							local currentValue = Settings:get(self.props.id)
							Settings:set(self.props.id, not currentValue)
						end,
					}),

				Reset = if self.props.onReset
					then e(IconButton, {
						icon = Assets.Images.Icons.Reset,
						iconSize = 24,
						color = settingsTheme.BackButtonColor,
						transparency = self.props.transparency,
						visible = self.props.showReset,
						layoutOrder = -1,

						onClick = self.props.onReset,
					})
					else nil,
			}),

			Text = e("Frame", {
				Size = UDim2.new(1, 0, 1, 0),
				BackgroundTransparency = 1,
			}, {
				Heading = e("Frame", {
					Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
					BackgroundTransparency = 1,
				}, {
					Layout = e("UIListLayout", {
						VerticalAlignment = Enum.VerticalAlignment.Center,
						FillDirection = Enum.FillDirection.Horizontal,
						SortOrder = Enum.SortOrder.LayoutOrder,
						Padding = UDim.new(0, 5),
					}),
					Tag = if self.props.tag and TAG_TYPES[self.props.tag]
						then e(Tag, {
							layoutOrder = 1,
							transparency = self.props.transparency,
							text = TAG_TYPES[self.props.tag].text,
							icon = TAG_TYPES[self.props.tag].icon,
							color = getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color),
						})
						else nil,
					Name = e("TextLabel", {
						Text = self.props.name,
						FontFace = theme.Font.Bold,
						TextSize = theme.TextSize.Medium,
						TextColor3 = if self.props.tag and TAG_TYPES[self.props.tag]
							then getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color)
							else settingsTheme.Setting.NameColor,
						TextXAlignment = Enum.TextXAlignment.Left,
						TextTransparency = self.props.transparency,
						RichText = true,

						Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),

						LayoutOrder = 2,
						BackgroundTransparency = 1,
					}),
				}),

				Description = e("TextLabel", {
					Text = self.props.description,
					FontFace = theme.Font.Main,
					LineHeight = 1.2,
					TextSize = theme.TextSize.Body,
					TextColor3 = settingsTheme.Setting.DescriptionColor,
					TextXAlignment = Enum.TextXAlignment.Left,
					TextTransparency = self.props.transparency,
					TextWrapped = true,
					RichText = true,

					Size = Roact.joinBindings({
						containerSize = self.containerSize,
						inputSize = self.inputSize,
					}):map(function(values)
						local offset = values.inputSize.X + 5
						local textBounds = getTextBoundsWithLineHeight(
							self.props.description,
							theme.Font.Main,
							theme.TextSize.Body,
							values.containerSize.X - offset,
							1.2
						)
						return UDim2.new(1, -offset, 0, textBounds.Y)
					end),

					LayoutOrder = 3,
					BackgroundTransparency = 1,
				}),

				Layout = e("UIListLayout", {
					VerticalAlignment = Enum.VerticalAlignment.Center,
					FillDirection = Enum.FillDirection.Vertical,
					SortOrder = Enum.SortOrder.LayoutOrder,
					Padding = UDim.new(0, 5),

					[Roact.Change.AbsoluteContentSize] = function(object)
						self.setContentSize(object.AbsoluteContentSize)
					end,
				}),
			}),

			Divider = e("Frame", {
				BackgroundColor3 = settingsTheme.DividerColor,
				BackgroundTransparency = self.props.transparency,
				Size = UDim2.new(1, 0, 0, 1),
				BorderSizePixel = 0,
			}, {
				Gradient = e("UIGradient", {
					Transparency = NumberSequence.new({
						NumberSequenceKeypoint.new(0, 1),
						NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
						NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
						NumberSequenceKeypoint.new(1, 1),
					}),
				}),
			}),
		})
	end)
end

return Setting