feat(ui): Update metadata and add new components for dashboard, form builder, and navigation menu

This commit is contained in:
2025-12-29 23:34:37 +00:00
parent f8efac1188
commit 26ba2d3ec0
11 changed files with 216 additions and 7 deletions

View File

@@ -2,11 +2,12 @@
"packageId": "dashboard",
"name": "Dashboard",
"version": "1.0.0",
"description": "Dashboard components",
"description": "Dashboard layouts, stat cards, and widgets",
"author": "MetaBuilder",
"category": "ui",
"dependencies": [],
"exports": {
"components": []
"components": ["StatCard", "DashboardGrid", "Widget"],
"scripts": ["stats", "layout"]
}
}

View File

@@ -2,11 +2,12 @@
"packageId": "form_builder",
"name": "Form Builder",
"version": "1.0.0",
"description": "Form builder components",
"description": "Form fields, validation, and submission handling",
"author": "MetaBuilder",
"category": "ui",
"dependencies": [],
"exports": {
"components": []
"components": ["FormField", "EmailField", "PasswordField", "NumberField", "SearchBar"],
"scripts": ["fields", "validate"]
}
}

View File

@@ -0,0 +1,53 @@
local M = {}
function M.text(props)
return {
type = "Box",
children = {
props.label and { type = "Label", props = { text = props.label, htmlFor = props.name } } or nil,
{ type = "Input", props = { name = props.name, placeholder = props.placeholder, required = props.required } }
}
}
end
function M.email(props)
return {
type = "Box",
children = {
{ type = "Label", props = { text = props.label or "Email", htmlFor = props.name } },
{ type = "Input", props = { name = props.name, type = "email", placeholder = "you@example.com" } }
}
}
end
function M.password(props)
return {
type = "Box",
children = {
{ type = "Label", props = { text = props.label or "Password", htmlFor = props.name } },
{ type = "Input", props = { name = props.name, type = "password", placeholder = "••••••••" } }
}
}
end
function M.number(props)
return {
type = "Box",
children = {
props.label and { type = "Label", props = { text = props.label, htmlFor = props.name } } or nil,
{ type = "Input", props = { name = props.name, type = "number", min = props.min, max = props.max } }
}
}
end
function M.textarea(props)
return {
type = "Box",
children = {
props.label and { type = "Label", props = { text = props.label, htmlFor = props.name } } or nil,
{ type = "TextArea", props = { name = props.name, rows = props.rows or 4, placeholder = props.placeholder } }
}
}
end
return M

View File

@@ -0,0 +1,3 @@
local M = {}
function M.on_install(ctx) return { message = "Form Builder installed", version = ctx.version } end
return M

View File

@@ -0,0 +1,7 @@
{
"scripts": [
{ "file": "init.lua", "name": "init", "category": "lifecycle", "description": "Package lifecycle" },
{ "file": "fields.lua", "name": "fields", "category": "ui", "description": "Form field renderers" },
{ "file": "validate.lua", "name": "validate", "category": "validation", "description": "Field validation" }
]
}

View File

@@ -0,0 +1,47 @@
local M = {}
function M.required(value)
return value ~= nil and value ~= ""
end
function M.email(value)
if not value then return false end
return string.match(value, "^[^@]+@[^@]+%.[^@]+$") ~= nil
end
function M.minLength(value, min)
return value and #value >= min
end
function M.maxLength(value, max)
return not value or #value <= max
end
function M.pattern(value, pat)
return value and string.match(value, pat) ~= nil
end
function M.number(value)
return tonumber(value) ~= nil
end
function M.range(value, min, max)
local n = tonumber(value)
return n and n >= min and n <= max
end
function M.validate_field(value, rules)
local errors = {}
for _, rule in ipairs(rules) do
if rule.type == "required" and not M.required(value) then
errors[#errors + 1] = rule.message or "Required"
elseif rule.type == "email" and not M.email(value) then
errors[#errors + 1] = rule.message or "Invalid email"
elseif rule.type == "minLength" and not M.minLength(value, rule.min) then
errors[#errors + 1] = rule.message or ("Min " .. rule.min .. " chars")
end
end
return { valid = #errors == 0, errors = errors }
end
return M

View File

@@ -2,11 +2,12 @@
"packageId": "nav_menu",
"name": "Navigation Menu",
"version": "1.0.0",
"description": "Navigation menu components",
"description": "Sidebar, navigation menus, and breadcrumbs",
"author": "MetaBuilder",
"category": "ui",
"dependencies": [],
"dependencies": ["ui_permissions"],
"exports": {
"components": []
"components": ["Sidebar", "NavigationMenu", "Breadcrumbs"],
"scripts": ["sidebar", "menu"]
}
}

View File

@@ -0,0 +1,3 @@
local M = {}
function M.on_install(ctx) return { message = "Nav Menu installed", version = ctx.version } end
return M

View File

@@ -0,0 +1,7 @@
{
"scripts": [
{ "file": "init.lua", "name": "init", "category": "lifecycle", "description": "Package lifecycle" },
{ "file": "sidebar.lua", "name": "sidebar", "category": "ui", "description": "Sidebar rendering" },
{ "file": "menu.lua", "name": "menu", "category": "ui", "description": "Navigation menu" }
]
}

View File

@@ -0,0 +1,44 @@
local check = require("check")
local M = {}
function M.render(props)
local items = {}
for _, item in ipairs(props.items or {}) do
if M.can_show(props.user, item) then
items[#items + 1] = M.item(item)
end
end
return {
type = "Flex",
props = { className = "items-center gap-4" },
children = items
}
end
function M.can_show(user, item)
if not item.minLevel then return true end
return check.can_access(user, item.minLevel)
end
function M.item(item)
if item.children then
return {
type = "DropdownMenu",
children = {
{ type = "DropdownMenuTrigger", props = { text = item.label } },
{ type = "DropdownMenuContent", children = M.sub_items(item.children) }
}
}
end
return { type = "Button", props = { variant = "ghost", text = item.label, onClick = "navigate", data = item.path } }
end
function M.sub_items(children)
local items = {}
for _, c in ipairs(children) do
items[#items + 1] = { type = "DropdownMenuItem", props = { text = c.label, onClick = "navigate", data = c.path } }
end
return items
end
return M

View File

@@ -0,0 +1,42 @@
local M = {}
function M.render(props)
local items = {}
for _, item in ipairs(props.items or {}) do
items[#items + 1] = M.item(item, props.currentPath)
end
return {
type = "Box",
props = { className = "w-64 border-r h-screen bg-sidebar" },
children = {
M.header(props),
{ type = "Stack", props = { spacing = 1, className = "p-4" }, children = items }
}
}
end
function M.header(props)
return {
type = "Box",
props = { className = "p-4 border-b" },
children = {
{ type = "Typography", props = { variant = "h6", text = props.title or "Menu" } }
}
}
end
function M.item(item, currentPath)
local active = currentPath == item.path
return {
type = "Button",
props = {
variant = active and "secondary" or "ghost",
className = "w-full justify-start",
text = item.label,
onClick = "navigate",
data = item.path
}
}
end
return M