config: packages,lua,menu (12 files)

This commit is contained in:
Richard Ward
2025-12-30 13:26:36 +00:00
parent b2b0ade653
commit d44e40440d
12 changed files with 275 additions and 141 deletions

View File

@@ -0,0 +1,10 @@
import React from 'react'
import { Icon, IconProps } from './Icon'
export const ClipboardCheck = (props: IconProps) => (
<Icon {...props}>
<rect x="40" y="40" width="176" height="176" rx="8" />
<path d="M88 24h80a8 8 0 0 1 8 8v16H80V32a8 8 0 0 1 8-8Z" />
<polyline points="84 140 112 168 172 108" />
</Icon>
)

View File

@@ -0,0 +1,10 @@
import React from 'react'
import { Icon, IconProps } from './Icon'
export const UserMinus = (props: IconProps) => (
<Icon {...props}>
<circle cx="96" cy="88" r="32" />
<path d="M32 200c0-35.3 28.7-64 64-64s64 28.7 64 64" />
<line x1="172" y1="120" x2="224" y2="120" />
</Icon>
)

View File

@@ -53,6 +53,8 @@ export { GearSix } from './GearSix'
export { User } from './User'
export { UserCircle } from './UserCircle'
export { UserPlus } from './UserPlus'
export { UserMinus } from './UserMinus'
export { UserX } from './UserX'
export { Users } from './Users'
export { UserSwitch } from './UserSwitch'
export { Menu } from './Menu'
@@ -149,6 +151,7 @@ export { Archive } from './Archive'
export { Bug } from './Bug'
export { Gavel } from './Gavel'
export { Clipboard } from './Clipboard'
export { ClipboardCheck } from './ClipboardCheck'
export { Package } from './Package'
export { Layers } from './Layers'
export { Tag } from './Tag'

View File

@@ -14,10 +14,12 @@ M.operationColors = {
--- Resource type to icon mapping
---@type table<string, string>
M.resourceIcons = {
user = "User",
credential = "ShieldCheck",
default = "ChartLine"
}
M.resourceIcons = {
user = "User",
credential = "ShieldCheck",
post = "ClipboardCheck",
settings = "Settings",
default = "ChartLine"
}
return M

View File

@@ -17,13 +17,30 @@ local M = {}
---@class User
---@field level? number
---@class MenuProps
---@field items MenuItem[]
---@field user User
---@param props MenuProps
---@return UIComponent
function M.render(props)
---@class MenuProps
---@field items MenuItem[]
---@field user User
---@class FlexProps
---@field className string
---@class ButtonProps
---@field variant string
---@field text string
---@field onClick string
---@field data? string
---@class DropdownMenuTriggerProps
---@field text string
---@class DropdownMenuItemProps
---@field text string
---@field onClick string
---@field data? string
---@param props MenuProps
---@return UIComponent
function M.render(props)
local items = {}
for _, item in ipairs(props.items or {}) do
if M.can_show(props.user, item) then

View File

@@ -0,0 +1,10 @@
{
"menu_item": [
{ "desc": "without icon", "label": "Home", "path": "/", "icon": null },
{ "desc": "with icon", "label": "Settings", "path": "/settings", "icon": "gear" }
],
"menu_group": [
{ "desc": "empty group", "label": "Admin", "children": null, "icon": null },
{ "desc": "empty children array", "label": "Settings", "children": [], "icon": "gear" }
]
}

View File

@@ -1,35 +1,42 @@
-- Items tests for nav_menu package
-- Tests menu item builder functions
local menu_item = require("items/item")
local menu_group = require("items/group")
local menu_divider = require("items/divider")
describe("Menu Item Builders", function()
describe("menu_item", function()
it.each({
{ label = "Home", path = "/", icon = nil, desc = "without icon" },
{ label = "Settings", path = "/settings", icon = "gear", desc = "with icon" },
})("should create item $desc", function(testCase)
local result = menu_item(testCase.label, testCase.path, testCase.icon)
expect(result.type).toBe("menu_item")
expect(result.label).toBe(testCase.label)
expect(result.path).toBe(testCase.path)
-- Items tests for nav_menu package
-- Tests menu item builder functions
---@class MenuItemCase
---@field label string
---@field path string
---@field icon string|nil
---@field desc string
---@class MenuGroupCase
---@field label string
---@field children table|nil
---@field icon string|nil
---@field desc string
local menu_item = require("items/item")
local menu_group = require("items/group")
local menu_divider = require("items/divider")
local cases = load_cases("items.cases.json")
describe("Menu Item Builders", function()
describe("menu_item", function()
it.each(cases.menu_item, "$desc", function(testCase)
local result = menu_item(testCase.label, testCase.path, testCase.icon)
expect(result.type).toBe("menu_item")
expect(result.label).toBe(testCase.label)
expect(result.path).toBe(testCase.path)
if testCase.icon then
expect(result.icon).toBe(testCase.icon)
end
end)
end)
describe("menu_group", function()
it.each({
{ label = "Admin", children = nil, icon = nil, desc = "empty group" },
{ label = "Settings", children = {}, icon = "gear", desc = "empty children array" },
})("should create group $desc", function(testCase)
local result = menu_group(testCase.label, testCase.children, testCase.icon)
expect(result.type).toBe("menu_group")
expect(result.label).toBe(testCase.label)
expect(result.children).toBeType("table")
describe("menu_group", function()
it.each(cases.menu_group, "$desc", function(testCase)
local result = menu_group(testCase.label, testCase.children, testCase.icon)
expect(result.type).toBe("menu_group")
expect(result.label).toBe(testCase.label)
expect(result.children).toBeType("table")
end)
it("should include children in group", function()

View File

@@ -20,15 +20,29 @@
---@field expected boolean
---@field desc string
---@class MenuItemRenderTestCase
---@field item MenuItem
---@field expectedType string
---@field expectedText? string
---@field expectedVariant? string
---@field expectedChildren? integer
---@field desc string
---@class MenuRenderTestCase
---@field props MenuRenderProps
---@field expectedChildren integer
---@field desc string
---@class MenuCases
---@field can_show MenuShowTestCase[]
---@field item MenuItemRenderTestCase[]
---@field render MenuRenderTestCase[]
describe("Menu", function()
-- Mock check module
---@type { can_show: MenuShowTestCase[], render: MenuRenderTestCase[] }
---@type MenuCases
local cases = load_cases("menu.cases.json")
local it_each = require("lua_test.it_each")
before(function()
-- Create mock for check.can_access
@@ -42,38 +56,39 @@ describe("Menu", function()
local menu = require("menu")
describe("can_show", function()
it.each(cases.can_show, "$desc", function(testCase)
local result = menu.can_show(testCase.user, testCase.item)
expect(result).toBe(testCase.expected)
it_each(cases.can_show, "$desc", function(testCase)
---@type MenuShowTestCase
local tc = testCase
local result = menu.can_show(tc.user, tc.item)
expect(result).toBe(tc.expected)
end)
end)
describe("item", function()
it("should render button for simple item", function()
local result = menu.item({ label = "Home", path = "/" })
expect(result.type).toBe("Button")
expect(result.props.text).toBe("Home")
expect(result.props.variant).toBe("ghost")
end)
it("should render dropdown for item with children", function()
local result = menu.item({
label = "Settings",
children = {
{ label = "Profile", path = "/profile" },
{ label = "Security", path = "/security" }
}
})
expect(result.type).toBe("DropdownMenu")
expect(#result.children).toBe(2)
end)
end)
describe("item", function()
it_each(cases.item, "$desc", function(testCase)
---@type MenuItemRenderTestCase
local tc = testCase
local result = menu.item(tc.item)
expect(result.type).toBe(tc.expectedType)
if tc.expectedText then
expect(result.props.text).toBe(tc.expectedText)
end
if tc.expectedVariant then
expect(result.props.variant).toBe(tc.expectedVariant)
end
if tc.expectedChildren then
expect(#result.children).toBe(tc.expectedChildren)
end
end)
end)
describe("render", function()
it.each(cases.render, "$desc", function(testCase)
local result = menu.render(testCase.props)
it_each(cases.render, "$desc", function(testCase)
---@type MenuRenderTestCase
local tc = testCase
local result = menu.render(tc.props)
expect(result.type).toBe("Flex")
expect(#result.children).toBe(testCase.expectedChildren)
expect(#result.children).toBe(tc.expectedChildren)
end)
end)
end)

View File

@@ -14,54 +14,58 @@
---@class ButtonProps
---@field text string
---@field onClick string
---@field variant? string
---@class UserInfo
---@field username string
---@field email? string
---@class RenderContext
---@field user UserInfo
---@class RenderContext
---@field user UserInfo
local M = {}
---Renders the user profile card with form inputs
---@param ctx RenderContext
---@return UIComponent
function M.render(ctx)
return {
type = "Card",
children = {
{
type = "CardHeader",
children = {
{ type = "CardTitle", props = { text = "Your Profile" } }
}
},
{
type = "CardContent",
children = {
{
type = "Input",
props = {
label = "Username",
value = ctx.user.username,
disabled = true
}
},
{
type = "Input",
props = {
label = "Email",
name = "email",
value = ctx.user.email or ""
}
},
{
type = "Button",
props = {
text = "Save Changes",
onClick = "saveProfile"
}
function M.render(ctx)
return {
type = "Card",
children = {
{
type = "CardHeader",
children = {
{ type = "CardTitle", props = { text = "Your Profile" } }
}
},
{
type = "CardContent",
children = {
{
type = "Input",
props = {
---@type InputProps
label = "Username",
value = ctx.user.username,
disabled = true
}
},
{
type = "Input",
props = {
---@type InputProps
label = "Email",
name = "email",
value = ctx.user.email or ""
}
},
{
type = "Button",
props = {
---@type ButtonProps
text = "Save Changes",
onClick = "saveProfile"
}
}
}
}

View File

@@ -2,14 +2,21 @@
-- Re-exports all moderation functions for backward compatibility
-- Each function is defined in its own file following 1-function-per-file pattern
---@class ModerationUpdates
---@field username? string
---@field email? string
---@field role? string
---@field level? number
---@field status? string
---@class Moderation
---@field deleteUser fun(userId: string): boolean
---@field editUser fun(userId: string, updates: table): boolean
---@field editUser fun(userId: string, updates: ModerationUpdates): boolean
---@field banUser fun(userId: string, reason: string): boolean
local M = {}
-- Import all single-function modules
local deleteUser = require("moderation.delete_user")
-- Import all single-function modules
local deleteUser = require("moderation.delete_user")
local editUser = require("moderation.edit_user")
local banUser = require("moderation.ban_user")

View File

@@ -8,11 +8,13 @@
---@class ModerationContext
---@field user ModerationUser User object for permission checking
---@field targetId string ID of the target user for moderation action
---@class ActionResult
---@field success boolean Whether the action was successful
---@field error string? Error message if unsuccessful
---@field action string? Action type to perform
---@field id string? ID of the affected user
---@alias ModerationAction "delete_user"|"ban_user"|"open_edit_dialog"
---@class ActionResult
---@field success boolean Whether the action was successful
---@field error string? Error message if unsuccessful
---@field action ModerationAction? Action type to perform
---@field id string? ID of the affected user
return {}

View File

@@ -39,31 +39,78 @@
---Renders the schemas tab with a grid of schema cards
---@param ctx SchemasRenderContext
---@return UIComponent
local function render(ctx)
local items = {}
for _, s in ipairs(ctx.schemas or {}) do
items[#items + 1] = {
type = "Card",
children = {
{ type = "CardHeader", children = { { type = "CardTitle", props = { text = s.name } } } },
{ type = "CardContent", children = {
{ type = "Typography", props = { text = s.description or "No description" } },
{ type = "Badge", props = { text = #(s.fields or {}) .. " fields" } }
}},
{ type = "CardFooter", children = {
{ type = "Button", props = { text = "Edit", onClick = "editSchema", data = s.id } }
}}
}
}
end
return {
type = "Stack",
props = { spacing = 4 },
children = {
{ type = "Button", props = { text = "Add Schema", onClick = "addSchema" } },
{ type = "Grid", props = { cols = 2, gap = 4 }, children = items }
}
}
end
local function render(ctx)
---@type UIComponent[]
local items = {}
for _, s in ipairs(ctx.schemas or {}) do
items[#items + 1] = {
type = "Card",
children = {
{
type = "CardHeader",
children = {
{ type = "CardTitle", props = { text = s.name } }
}
},
{
type = "CardContent",
children = {
{
type = "Typography",
props = {
---@type TypographyProps
text = s.description or "No description"
}
},
{
type = "Badge",
props = {
---@type BadgeProps
text = #(s.fields or {}) .. " fields"
}
}
}
},
{
type = "CardFooter",
children = {
{
type = "Button",
props = {
---@type ButtonProps
text = "Edit",
onClick = "editSchema",
data = s.id
}
}
}
}
}
}
end
return {
type = "Stack",
props = { spacing = 4 },
children = {
{
type = "Button",
props = {
---@type ButtonProps
text = "Add Schema",
onClick = "addSchema"
}
},
{
type = "Grid",
props = {
---@type GridProps
cols = 2,
gap = 4
},
children = items
}
}
}
end
return render