-- Services
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")
local TextService = game:GetService("TextService")
local Players = game:GetService("Players")
-- Utility: Dynamic Instance Constructor
local function Create(className, properties)
local instance = Instance.new(className)
for property, value in pairs(properties) do
instance[property] = value
end
return instance
end
-- Centralized Theme System
local Theme = {
Background = Color3.fromRGB(25, 25, 25),
Elevated = Color3.fromRGB(35, 35, 35),
Text = Color3.fromRGB(240, 240, 240),
TextSecondary = Color3.fromRGB(180, 180, 180),
Accent = Color3.fromRGB(0, 120, 215),
Border = Color3.fromRGB(60, 60, 60),
Hover = Color3.fromRGB(45, 45, 45),
Font = Enum.Font.Gotham,
TitleFont = Enum.Font.GothamBold,
Padding = 10,
CornerRadius = 8,
UIScale = 0.75
}
-- Janitor for Resource Management
local Janitor = {}
Janitor.__index = Janitor
function Janitor.new()
return setmetatable({_tasks = {}}, Janitor)
end
function Janitor:Add(task)
table.insert(self._tasks, task)
return task
end
function Janitor:Destroy()
for _, task in ipairs(self._tasks) do
if type(task) == "function" then
task()
elseif typeof(task) == "RBXScriptConnection" then
task:Disconnect()
elseif typeof(task) == "Instance" then
task:Destroy()
end
end
self._tasks = {}
end
-- Custom Signal Class
local Signal = {}
Signal.__index = Signal
function Signal.new()
return setmetatable({_connections = {}}, Signal)
end
function Signal:Connect(callback)
local connection = {Connected = true}
connection.Disconnect = function()
connection.Connected = false
for i, conn in ipairs(self._connections) do
if conn == connection then
table.remove(self._connections, i)
break
end
end
end
connection.Callback = callback
table.insert(self._connections, connection)
return connection
end
function Signal:Fire(...)
for _, connection in ipairs(self._connections) do
if connection.Connected then
task.spawn(connection.Callback, ...)
end
end
end
-- Paragraph Module
local Paragraph = {}
Paragraph.__index = Paragraph
function Paragraph.new(config)
local self = setmetatable({}, Paragraph)
self._janitor = Janitor.new()
self.OnClose = Signal.new()
self._isVisible = true
self._title = config.Title or "Information"
self._content = config.Content or "This is a paragraph of text."
self._width = config.Width or 400
self._maxHeight = config.MaxHeight or 500
self:_createUI(config)
self:_setupInteractions()
return self
end
function Paragraph:_createUI(config)
-- Main Container
self.Container = self._janitor:Add(Create("Frame", {
Size = UDim2.new(0, self._width, 0, 100),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundColor3 = Theme.Elevated,
BorderSizePixel = 0,
Parent = config.Parent or Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("ScreenGui") or Create("ScreenGui", {Parent = Players.LocalPlayer:WaitForChild("PlayerGui")})
}))
Create("UICorner", {
CornerRadius = UDim.new(0, Theme.CornerRadius),
Parent = self.Container
})
Create("UIStroke", {
Color = Theme.Border,
Thickness = 1,
Parent = self.Container
})
Create("UIScale", {
Scale = Theme.UIScale,
Parent = self.Container
})
-- Header Bar
self.Header = Create("Frame", {
Size = UDim2.new(1, 0, 0, 45),
BackgroundColor3 = Theme.Background,
BorderSizePixel = 0,
Parent = self.Container
})
Create("UICorner", {
CornerRadius = UDim.new(0, Theme.CornerRadius),
Parent = self.Header
})
-- Title
self.TitleLabel = Create("TextLabel", {
Size = UDim2.new(1, -50, 1, 0),
Position = UDim2.new(0, Theme.Padding, 0, 0),
BackgroundTransparency = 1,
Text = self._title,
TextColor3 = Theme.Text,
Font = Theme.TitleFont,
TextSize = 16,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
Parent = self.Header
})
-- Close Button
self.CloseButton = Create("TextButton", {
Size = UDim2.new(0, 35, 0, 35),
Position = UDim2.new(1, -40, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
BackgroundColor3 = Theme.Background,
BackgroundTransparency = 1,
Text = "✕",
TextColor3 = Theme.TextSecondary,
Font = Theme.Font,
TextSize = 18,
Parent = self.Header
})
Create("UICorner", {
CornerRadius = UDim.new(0, 6),
Parent = self.CloseButton
})
-- Content Container with Scrolling
self.ContentFrame = Create("ScrollingFrame", {
Size = UDim2.new(1, -20, 1, -55),
Position = UDim2.new(0, 10, 0, 50),
BackgroundTransparency = 1,
BorderSizePixel = 0,
ScrollBarThickness = 4,
ScrollBarImageColor3 = Theme.Border,
CanvasSize = UDim2.new(0, 0, 0, 0),
AutomaticCanvasSize = Enum.AutomaticSize.Y,
Parent = self.Container
})
-- Content Text
self.ContentLabel = Create("TextLabel", {
Size = UDim2.new(1, -10, 0, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
Text = self._content,
TextColor3 = Theme.Text,
Font = Theme.Font,
TextSize = 14,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextWrapped = true,
RichText = true,
LineHeight = 1.4,
Parent = self.ContentFrame
})
Create("UIPadding", {
PaddingLeft = UDim.new(0, 5),
PaddingRight = UDim.new(0, 5),
PaddingTop = UDim.new(0, 5),
PaddingBottom = UDim.new(0, 10),
Parent = self.ContentFrame
})
-- Auto-resize container based on content
task.wait()
self:_updateSize()
end
function Paragraph:_updateSize()
local contentHeight = self.ContentLabel.AbsoluteSize.Y + 65
local finalHeight = math.min(contentHeight, self._maxHeight)
TweenService:Create(self.Container, TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {
Size = UDim2.new(0, self._width, 0, finalHeight)
}):Play()
end
function Paragraph:_setupInteractions()
-- Close button hover effect
local hoverTween
self._janitor:Add(self.CloseButton.MouseEnter:Connect(function()
if hoverTween then hoverTween:Cancel() end
hoverTween = TweenService:Create(self.CloseButton, TweenInfo.new(0.15, Enum.EasingStyle.Quad), {
BackgroundTransparency = 0,
BackgroundColor3 = Theme.Hover,
TextColor3 = Theme.Text
})
hoverTween:Play()
end))
self._janitor:Add(self.CloseButton.MouseLeave:Connect(function()
if hoverTween then hoverTween:Cancel() end
hoverTween = TweenService:Create(self.CloseButton, TweenInfo.new(0.15, Enum.EasingStyle.Quad), {
BackgroundTransparency = 1,
TextColor3 = Theme.TextSecondary
})
hoverTween:Play()
end))
-- Close button click
self._janitor:Add(self.CloseButton.MouseButton1Click:Connect(function()
self:Close()
end))
-- Make draggable
self:_makeDraggable()
end
function Paragraph:_makeDraggable()
local dragging, dragInput, dragStart, startPos
self._janitor:Add(self.Header.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
dragStart = input.Position
startPos = self.Container.Position
input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End then
dragging = false
end
end)
end
end))
self._janitor:Add(self.Header.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
dragInput = input
end
end))
self._janitor:Add(UserInputService.InputChanged:Connect(function(input)
if input == dragInput and dragging then
local delta = input.Position - dragStart
self.Container.Position = UDim2.new(
startPos.X.Scale,
startPos.X.Offset + delta.X,
startPos.Y.Scale,
startPos.Y.Offset + delta.Y
)
end
end))
end
function Paragraph:SetTitle(newTitle)
self._title = newTitle
self.TitleLabel.Text = newTitle
TweenService:Create(self.TitleLabel, TweenInfo.new(0.2, Enum.EasingStyle.Quad), {
TextColor3 = Theme.Accent
}):Play()
task.delay(0.2, function()
TweenService:Create(self.TitleLabel, TweenInfo.new(0.3, Enum.EasingStyle.Quad), {
TextColor3 = Theme.Text
}):Play()
end)
end
function Paragraph:SetContent(newContent)
self._content = newContent
self.ContentLabel.Text = newContent
task.wait()
self:_updateSize()
end
function Paragraph:Toggle()
self._isVisible = not self._isVisible
self.Container.Visible = self._isVisible
if self._isVisible then
self:_playShowAnimation()
end
end
function Paragraph:Show()
self._isVisible = true
self.Container.Visible = true
self:_playShowAnimation()
end
function Paragraph:_playShowAnimation()
self.Container.Size = UDim2.new(0, self._width, 0, 0)
local contentHeight = self.ContentLabel.AbsoluteSize.Y + 65
local finalHeight = math.min(contentHeight, self._maxHeight)
TweenService:Create(self.Container, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {
Size = UDim2.new(0, self._width, 0, finalHeight)
}):Play()
end
function Paragraph:Close()
local tween = TweenService:Create(self.Container, TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {
Size = UDim2.new(0, self._width, 0, 0)
})
tween:Play()
tween.Completed:Connect(function()
self._isVisible = false
self.Container.Visible = false
self.OnClose:Fire()
end)
end
function Paragraph:Destroy()
self._janitor:Destroy()
end
-- Example Usage
local screenGui = Create("ScreenGui", {
Parent = Players.LocalPlayer:WaitForChild("PlayerGui"),
ResetOnSpawn = false
})
local exampleContent = [[Welcome to the Professional Paragraph System!
This UI component demonstrates several advanced features including rich text formatting, automatic content sizing, smooth animations, and responsive design patterns.
Key Features:
• Dynamic height adjustment based on content length
• Smooth expand and collapse animations using TweenService
• Draggable header for repositioning
• Scrollable content area for lengthy paragraphs
• Clean, professional dark theme design
• Proper resource management with Janitor pattern
The text supports bold formatting, italic text, and underlined content. You can also use custom colors to highlight important information.
Try dragging this window around by clicking and holding the header bar. The close button in the top-right corner will smoothly animate the window closed when clicked.]]
local paragraph = Paragraph.new({
Parent = screenGui,
Title = "System Documentation",
Content = exampleContent,
Width = 450,
MaxHeight = 550
})
-- Listen for close event
paragraph.OnClose:Connect(function()
print("Paragraph closed")
end)
-- Example: Toggle with keybind
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if not gameProcessed and input.KeyCode == Enum.KeyCode.P then
paragraph:Toggle()
end
end)
-- Example: Update content dynamically
task.delay(5, function()
paragraph:SetTitle("Updated Information")
paragraph:SetContent("This content was updated after 5 seconds to demonstrate the dynamic updating capabilities of the paragraph system. The container automatically resizes to fit the new content!")
end)