mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Addded dropdown component with customizable options
This commit is contained in:
parent
59a57f79c8
commit
5362f7ecee
1 changed files with 253 additions and 0 deletions
253
src/dropdown.luau
Normal file
253
src/dropdown.luau
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
-- src/dropdown.luau
|
||||
local create = require "./create"
|
||||
local source = require "./source"
|
||||
|
||||
return function(props)
|
||||
props = props or {}
|
||||
|
||||
local value = props.value
|
||||
if value == nil then
|
||||
value = source(props.default)
|
||||
end
|
||||
if type(value) ~= "function" then
|
||||
error("vide.dropdown: props.value must be a Source<T> (callable), or omitted for internal state", 2)
|
||||
end
|
||||
|
||||
local open = props.open
|
||||
if open == nil then
|
||||
open = source(props.defaultOpen == true)
|
||||
end
|
||||
if type(open) ~= "function" then
|
||||
error("vide.dropdown: props.open must be a Source<boolean> (callable), or omitted for internal state", 2)
|
||||
end
|
||||
|
||||
local options = props.options or {}
|
||||
local closeOnSelect = (props.closeOnSelect ~= false)
|
||||
|
||||
-- Sizing
|
||||
local width = props.width or 160
|
||||
local height = props.height or 44
|
||||
local rowHeight = props.rowHeight or 36
|
||||
local gap = props.gap or 8
|
||||
local maxVisible = props.maxVisible or 4
|
||||
|
||||
-- Theme (neutral only applied if provided)
|
||||
local theme = props.theme or {}
|
||||
|
||||
local boxBg = theme.boxBg
|
||||
local boxBgT = theme.boxBgTransparency
|
||||
local boxStroke = theme.boxStroke
|
||||
local boxStrokeT = theme.boxStrokeTransparency
|
||||
|
||||
local listBg = theme.listBg
|
||||
local listBgT = theme.listBgTransparency
|
||||
local listStroke = theme.listStroke
|
||||
local listStrokeT = theme.listStrokeTransparency
|
||||
|
||||
local textColor = theme.textColor
|
||||
local itemBg = theme.itemBg
|
||||
local itemBgT = theme.itemBgTransparency
|
||||
|
||||
local radius = theme.cornerRadius or 12
|
||||
local itemRadius = theme.itemCornerRadius or 10
|
||||
|
||||
-- Helpers
|
||||
local function Corner(px)
|
||||
return create "UICorner" { CornerRadius = UDim.new(0, px) }
|
||||
end
|
||||
|
||||
local function Stroke(color, transparency)
|
||||
if color == nil and transparency == nil then
|
||||
return nil
|
||||
end
|
||||
return create "UIStroke" {
|
||||
Color = color,
|
||||
Transparency = transparency,
|
||||
Thickness = 1,
|
||||
}
|
||||
end
|
||||
|
||||
local function set(v)
|
||||
value(v)
|
||||
if props.onChanged then
|
||||
props.onChanged(v)
|
||||
end
|
||||
end
|
||||
|
||||
local function toggleOpen()
|
||||
open(not open())
|
||||
if open() and props.onOpen then props.onOpen() end
|
||||
if (not open()) and props.onClose then props.onClose() end
|
||||
end
|
||||
|
||||
-- Option model:
|
||||
-- - string/number/etc: label = tostring(opt), value = opt
|
||||
-- - table: { label = "All", value = "All", disabled = true }
|
||||
local function normalize(opt)
|
||||
if type(opt) == "table" then
|
||||
return opt.label, opt.value, opt.disabled == true
|
||||
end
|
||||
return tostring(opt), opt, false
|
||||
end
|
||||
|
||||
local function currentLabel()
|
||||
local v = value()
|
||||
if v == nil then
|
||||
return props.placeholder or ""
|
||||
end
|
||||
-- options have tables match value thing
|
||||
for _, opt in ipairs(options) do
|
||||
local label, optValue = normalize(opt)
|
||||
if optValue == v then
|
||||
return label
|
||||
end
|
||||
end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
return create "Frame" {
|
||||
Name = props.name or "Dropdown",
|
||||
Size = props.size or UDim2.fromOffset(width, height),
|
||||
Position = props.position,
|
||||
AnchorPoint = props.anchorPoint,
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
ClipsDescendants = false,
|
||||
|
||||
-- (collapsed view)
|
||||
create "TextButton" {
|
||||
Name = "Button",
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
|
||||
Text = function()
|
||||
return currentLabel()
|
||||
end,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextSize = props.textSize or 16,
|
||||
Font = props.font or Enum.Font.GothamBold,
|
||||
TextColor3 = textColor,
|
||||
|
||||
AutoButtonColor = true,
|
||||
Active = not props.disabled,
|
||||
Selectable = true,
|
||||
|
||||
BackgroundColor3 = boxBg,
|
||||
BackgroundTransparency = boxBgT,
|
||||
|
||||
Corner(radius),
|
||||
Stroke(boxStroke, boxStrokeT),
|
||||
|
||||
create "UIPadding" {
|
||||
PaddingTop = UDim.new(0, 10),
|
||||
PaddingBottom = UDim.new(0, 10),
|
||||
PaddingLeft = UDim.new(0, 12),
|
||||
PaddingRight = UDim.new(0, 12),
|
||||
},
|
||||
|
||||
Activated = function()
|
||||
if props.disabled then return end
|
||||
toggleOpen()
|
||||
end,
|
||||
},
|
||||
|
||||
-- Popup list (only rendered when open)
|
||||
function()
|
||||
if not open() then
|
||||
return nil
|
||||
end
|
||||
|
||||
local visible = math.min(#options, maxVisible)
|
||||
local popupH = 16 + (visible * rowHeight) + ((visible - 1) * gap) -- 8 top + 8 bottom padding
|
||||
|
||||
local function optionRow(i, opt)
|
||||
local label, optValue, disabled = normalize(opt)
|
||||
|
||||
return create "TextButton" {
|
||||
LayoutOrder = i,
|
||||
Size = UDim2.new(1, 0, 0, rowHeight),
|
||||
|
||||
Text = label,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextSize = props.textSize or 16,
|
||||
Font = props.font or Enum.Font.Gotham,
|
||||
TextColor3 = textColor,
|
||||
|
||||
AutoButtonColor = true,
|
||||
Active = not disabled,
|
||||
Selectable = not disabled,
|
||||
|
||||
BackgroundColor3 = itemBg,
|
||||
BackgroundTransparency = itemBgT,
|
||||
|
||||
Corner(itemRadius),
|
||||
|
||||
create "UIPadding" {
|
||||
PaddingTop = UDim.new(0, 8),
|
||||
PaddingBottom = UDim.new(0, 8),
|
||||
PaddingLeft = UDim.new(0, 10),
|
||||
PaddingRight = UDim.new(0, 10),
|
||||
},
|
||||
|
||||
Activated = function()
|
||||
if disabled then return end
|
||||
set(optValue)
|
||||
if closeOnSelect then
|
||||
open(false)
|
||||
if props.onClose then props.onClose() end
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return create "Frame" {
|
||||
Name = "List",
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
Position = UDim2.new(0, 0, 1, 8),
|
||||
Size = UDim2.new(1, 0, 0, popupH),
|
||||
|
||||
BackgroundColor3 = listBg,
|
||||
BackgroundTransparency = listBgT,
|
||||
|
||||
Corner(radius),
|
||||
Stroke(listStroke, listStrokeT),
|
||||
|
||||
ClipsDescendants = true,
|
||||
|
||||
create "UIPadding" {
|
||||
PaddingTop = UDim.new(0, 8),
|
||||
PaddingBottom = UDim.new(0, 8),
|
||||
PaddingLeft = UDim.new(0, 8),
|
||||
PaddingRight = UDim.new(0, 8),
|
||||
},
|
||||
|
||||
-- Scroll container (works even for small lists)
|
||||
create "ScrollingFrame" {
|
||||
Name = "Scroll",
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
|
||||
ScrollBarThickness = props.scrollBarThickness or 6,
|
||||
ScrollingDirection = Enum.ScrollingDirection.Y,
|
||||
AutomaticCanvasSize = Enum.AutomaticSize.Y,
|
||||
CanvasSize = UDim2.new(0, 0, 0, 0),
|
||||
|
||||
create "UIListLayout" {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
Padding = UDim.new(0, gap),
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
},
|
||||
|
||||
(function()
|
||||
local children = {}
|
||||
for i, opt in ipairs(options) do
|
||||
children[i] = optionRow(i, opt)
|
||||
end
|
||||
return children
|
||||
end)(),
|
||||
},
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue