ContextMenu

The Context Menu System provides contextual menus that appear at specific screen positions, perfect for right-click interactions, entity-specific actions, and location-based options.

Dark Context Menu

registerContextMenu

Register a context menu definition that can be shown at specific positions with comprehensive configuration options.

-- Client-side
B2Lib.UI.registerContextMenu(contextId, menuData)

-- Server-side (via exports)
exports.b2lib:registerContextMenu(contextId, menuData)

Parameters:

  • contextId (string, required): Unique context menu identifier for registration and management

  • menuData (table, required): Comprehensive context menu configuration object

Menu Data Options:

  • title (string, optional): Context menu title displayed at the top (default: empty string)

  • items (table, optional): Array of context menu items with full configuration (default: empty array)

  • maxWidth (number, optional): Maximum menu width in pixels with responsive scaling (default: 250)

  • minWidth (number, optional): Minimum menu width in pixels (default: 180)

  • showIcons (boolean, optional): Display item icons with proper spacing (default: true)

  • showShortcuts (boolean, optional): Display keyboard shortcuts for items (default: true)

  • autoClose (boolean, optional): Auto-close menu after item selection (default: true)

  • position (string, optional): Default positioning strategy ('auto', 'fixed', 'follow-cursor')

  • animation (string, optional): Animation type ('fade', 'slide', 'scale', 'none')

  • theme (string, optional): Theme override ('auto', 'light', 'dark')

  • zIndex (number, optional): Z-index for menu layering (default: 1000)

Returns: boolean - Success status indicating registration completion

Example:

-- Register a basic context menu
local success = B2Lib.UI.registerContextMenu('player_context', {
    title = 'Player Actions',
    items = {
        { label = 'Send Message', value = 'message', icon = 'message-circle' },
        { label = 'Add Friend', value = 'friend', icon = 'user-plus' },
        { label = 'Report Player', value = 'report', icon = 'flag' }
    }
})

-- Register a vehicle context menu
B2Lib.UI.registerContextMenu('vehicle_context', {
    title = 'Vehicle Options',
    maxWidth = 200,
    items = {
        { label = 'Enter Vehicle', value = 'enter', icon = 'car' },
        { label = 'Lock/Unlock', value = 'lock', icon = 'lock' },
        { label = 'Inspect', value = 'inspect', icon = 'search' },
        { label = 'Steal', value = 'steal', icon = 'key', disabled = true }
    }
})

-- Register a world context menu
B2Lib.UI.registerContextMenu('world_context', {
    items = {
        { label = 'Drop Item', value = 'drop', icon = 'package' },
        { label = 'Place Marker', value = 'marker', icon = 'map-pin' },
        { label = 'Take Screenshot', value = 'screenshot', icon = 'camera' }
    }
})

showContextMenu

Display a registered context menu at specific screen coordinates with dynamic context data and smart positioning.

-- Client-side
B2Lib.UI.showContextMenu(contextId, position, contextData)

-- Server-side (via exports)
exports.b2lib:showContextMenu(playerId, contextId, position, contextData)

Parameters:

  • playerId (number, server-side only): Player server ID to show context menu to

  • contextId (string, required): Registered context menu identifier to display

  • position (table, required): Position coordinates with smart boundary detection

    • x (number): X coordinate in pixels from left edge of screen

    • y (number): Y coordinate in pixels from top edge of screen

    • anchor (string, optional): Anchor point ('top-left', 'top-right', 'bottom-left', 'bottom-right')

  • contextData (table, optional): Dynamic context data passed to menu items and event handlers

Returns: boolean - Success status indicating menu display completion

Example:

-- Show context menu at mouse position
local mouseX, mouseY = GetNuiCursorPosition()
local success = B2Lib.UI.showContextMenu('player_context', {
    x = mouseX,
    y = mouseY
}, {
    playerId = targetPlayerId,
    playerName = GetPlayerName(targetPlayerId)
})

-- Show context menu at specific coordinates
B2Lib.UI.showContextMenu('vehicle_context', {
    x = 500,
    y = 300
}, {
    vehicleId = targetVehicle,
    isLocked = GetVehicleDoorLockStatus(targetVehicle) == 2,
    canSteal = hasLockpicks
})

-- Show world context menu with item data
B2Lib.UI.showContextMenu('world_context', {
    x = screenX,
    y = screenY
}, {
    worldPos = GetEntityCoords(PlayerPedId()),
    hasItems = #playerInventory > 0
})

hideContextMenu

Hide the currently active context menu with smooth animation and proper cleanup.

-- Client-side
B2Lib.UI.hideContextMenu()

-- Server-side (via exports)
exports.b2lib:hideContextMenu(playerId)

Parameters:

  • playerId (number, server-side only): Player server ID to hide context menu for

Returns: boolean - Success status indicating menu hide completion

Example:

-- Hide the current context menu
local success = B2Lib.UI.hideContextMenu()

-- Hide context menu when player moves away
CreateThread(function()
    while true do
        Wait(100)
        
        local playerCoords = GetEntityCoords(PlayerPedId())
        local distance = #(playerCoords - targetCoords)
        
        if distance > 5.0 and contextMenuVisible then
            B2Lib.UI.hideContextMenu()
            contextMenuVisible = false
        end
    end
end)

-- Server-side hide for specific player
exports.b2lib:hideContextMenu(playerId)

updateContextData

Update context data for the currently active context menu with real-time data refresh.

B2Lib.UI.updateContextData(contextData)

Parameters:

  • contextData (table, required): Updated context data object with new values

Returns: boolean - Success status indicating data update completion

Example:

-- Update context data dynamically
local success = B2Lib.UI.updateContextData({
    playerId = newTargetId,
    playerName = GetPlayerName(newTargetId),
    isOnline = true,
    distance = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(GetPlayerPed(newTargetId)))
})

-- Update vehicle context data
B2Lib.UI.updateContextData({
    vehicle = newVehicle,
    isLocked = GetVehicleDoorLockStatus(newVehicle) == 2,
    fuel = GetVehicleFuelLevel(newVehicle),
    health = GetEntityHealth(newVehicle)
})

isContextMenuVisible

Check if a context menu is currently visible and get active menu information.

B2Lib.UI.isContextMenuVisible()

Returns: boolean|table - False if no context menu is active, or table with active menu data

  • contextId (string): Current context menu identifier

  • position (table): Current menu position coordinates

  • contextData (table): Current context data

  • itemCount (number): Number of menu items

Example:

local menuState = B2Lib.UI.isContextMenuVisible()
if menuState then
    print('Context menu is visible:', menuState.contextId)
    print('Position:', json.encode(menuState.position))
    print('Item count:', menuState.itemCount)
else
    print('No context menu is currently visible')
end

updateContextMenu

Update a registered context menu's configuration and items dynamically.

B2Lib.UI.updateContextMenu(contextId, menuData)

Parameters:

  • contextId (string, required): Context menu identifier to update

  • menuData (table, required): Updated menu configuration object

Returns: boolean - Success status indicating menu update completion

Example:

-- Update menu items based on player state
local newItems = {
    { label = 'Send Message', value = 'message', icon = 'message-circle' },
    { label = 'Add Friend', value = 'friend', icon = 'user-plus' }
}

if isPlayerAdmin() then
    table.insert(newItems, { label = 'Admin Panel', value = 'admin', icon = 'shield', color = 'warning' })
end

B2Lib.UI.updateContextMenu('player_context', {
    title = 'Player Actions',
    items = newItems
})

Context Menu Item Structure

Context menu items support comprehensive properties for advanced functionality and visual customization:

local contextItem = {
    label = 'Item Label',                    -- Display text (required)
    value = 'item_value',                    -- Item value/ID for identification (required)
    description = 'Item description',        -- Optional description/tooltip text
    icon = 'icon_name',                      -- Optional Lucide icon name
    disabled = false,                        -- Whether item is disabled/non-interactive
    separator = false,                       -- Show separator line after this item
    color = 'default',                       -- Item color theme: 'default', 'danger', 'warning', 'success', 'info'
    shortcut = 'Ctrl+C',                     -- Optional keyboard shortcut display text
    badge = 'NEW',                           -- Optional badge text for notifications
    badgeColor = 'info',                     -- Badge color theme
    submenu = 'submenu_id',                  -- Optional submenu identifier for nested menus
    condition = function(contextData)        -- Optional condition function for dynamic visibility
        return contextData.hasPermission
    end,
    onClick = function(contextData)          -- Optional custom click handler
        print('Custom action executed')
    end,
    style = {                                -- Optional custom styling
        fontSize = '14px',
        fontWeight = 'bold',
        textColor = '#ff0000'
    }
}

Server-Side Usage

The Context Menu module provides comprehensive server-side functionality for managing player-specific context menus and administrative tools:

-- Register context menu on server
exports.b2lib:registerContextMenu('admin_context', {
    title = 'Admin Tools',
    items = {
        { label = 'Teleport Player', value = 'teleport', icon = 'zap' },
        { label = 'Kick Player', value = 'kick', icon = 'user-x', color = 'danger' },
        { label = 'Ban Player', value = 'ban', icon = 'shield-off', color = 'danger' },
        { separator = true },
        { label = 'Give Money', value = 'give_money', icon = 'dollar-sign' },
        { label = 'Set Job', value = 'set_job', icon = 'briefcase' }
    }
})

-- Show context menu to specific player
exports.b2lib:showContextMenu(playerId, 'admin_context', {
    x = 500,
    y = 300
}, {
    targetPlayerId = targetId,
    targetName = GetPlayerName(targetId),
    adminLevel = getPlayerAdminLevel(playerId)
})

-- Hide context menu for specific player
exports.b2lib:hideContextMenu(playerId)

-- Show context menu to multiple players
local function showContextMenuToPlayers(playerIds, contextId, position, contextData)
    for _, playerId in ipairs(playerIds) do
        exports.b2lib:showContextMenu(playerId, contextId, position, contextData)
    end
end

-- Show context menu to all admins
local function showAdminContextMenu(contextId, position, contextData)
    local players = GetPlayers()
    for _, playerId in ipairs(players) do
        if IsPlayerAceAllowed(playerId, 'admin') then
            exports.b2lib:showContextMenu(tonumber(playerId), contextId, position, contextData)
        end
    end
end

-- Example: Player management system
RegisterNetEvent('admin:showPlayerContext')
AddEventHandler('admin:showPlayerContext', function(targetPlayerId, position)
    local source = source
    
    -- Verify admin permissions
    if IsPlayerAceAllowed(source, 'admin.player_management') then
        local targetName = GetPlayerName(targetPlayerId)
        local targetPing = GetPlayerPing(targetPlayerId)
        
        exports.b2lib:showContextMenu(source, 'admin_player_context', position, {
            targetId = targetPlayerId,
            targetName = targetName,
            targetPing = targetPing,
            adminId = source,
            timestamp = os.time()
        })
    else
        TriggerClientEvent('b2lib:notify', source, {
            type = 'error',
            message = 'Insufficient permissions'
        })
    end
end)

-- Example: Dynamic context menu based on player state
local function getPlayerContextMenu(playerId, targetPlayerId)
    local adminLevel = getPlayerAdminLevel(playerId)
    local items = {}
    
    -- Basic actions for all players
    table.insert(items, { label = 'Send Message', value = 'message', icon = 'message-circle' })
    table.insert(items, { label = 'View Profile', value = 'profile', icon = 'user' })
    
    -- Moderator actions
    if adminLevel >= 1 then
        table.insert(items, { separator = true })
        table.insert(items, { label = 'Warn Player', value = 'warn', icon = 'alert-triangle', color = 'warning' })
        table.insert(items, { label = 'Mute Player', value = 'mute', icon = 'volume-x', color = 'warning' })
    end
    
    -- Admin actions
    if adminLevel >= 2 then
        table.insert(items, { separator = true })
        table.insert(items, { label = 'Kick Player', value = 'kick', icon = 'user-x', color = 'danger' })
        table.insert(items, { label = 'Ban Player', value = 'ban', icon = 'shield-off', color = 'danger' })
    end
    
    -- Super admin actions
    if adminLevel >= 3 then
        table.insert(items, { separator = true })
        table.insert(items, { label = 'Teleport To', value = 'teleport_to', icon = 'zap' })
        table.insert(items, { label = 'Spectate', value = 'spectate', icon = 'eye' })
    end
    
    return {
        title = 'Player: ' .. GetPlayerName(targetPlayerId),
        items = items
    }
end

-- Example: Zone-based context menus
local function showZoneContextMenu(coords, radius, contextId, contextData)
    local players = GetPlayers()
    
    for _, playerId in ipairs(players) do
        local playerPed = GetPlayerPed(playerId)
        if playerPed and DoesEntityExist(playerPed) then
            local playerCoords = GetEntityCoords(playerPed)
            local distance = #(playerCoords - coords)
            
            if distance <= radius then
                exports.b2lib:showContextMenu(tonumber(playerId), contextId, {
                    x = 400,
                    y = 300
                }, contextData)
            end
        end
    end
end

-- Example: Event-based context menu management
RegisterNetEvent('server:showVehicleContext')
AddEventHandler('server:showVehicleContext', function(vehicleNetId, position)
    local source = source
    local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
    
    if DoesEntityExist(vehicle) then
        local plate = GetVehicleNumberPlateText(vehicle)
        local model = GetEntityModel(vehicle)
        local displayName = GetDisplayNameFromVehicleModel(model)
        
        exports.b2lib:showContextMenu(source, 'vehicle_context', position, {
            vehicle = vehicle,
            plate = plate,
            model = model,
            displayName = displayName,
            playerId = source
        })
    end
end)

Events

The Context Menu module provides comprehensive event handling for monitoring and responding to context menu interactions:

Client Events

-- Context menu item selected event
AddEventHandler('b2lib:context-item-selected', function(data)
    print('Context item selected:', data.value)
    print('Context ID:', data.contextId)
    print('Item data:', json.encode(data.item))
    print('Context data:', json.encode(data.contextData))
    print('Mouse position:', json.encode(data.mousePosition))
    print('Timestamp:', data.timestamp)
end)

-- Context menu opened event
AddEventHandler('b2lib:context-opened', function(data)
    print('Context menu opened:', data.contextId)
    print('Position:', json.encode(data.position))
    print('Context data:', json.encode(data.contextData))
    print('Item count:', data.itemCount)
    print('Menu dimensions:', json.encode(data.dimensions))
end)

-- Context menu closed event
AddEventHandler('b2lib:context-closed', function(data)
    print('Context menu closed:', data.contextId)
    print('Close reason:', data.reason) -- 'selection', 'outside-click', 'escape', 'manual'
    print('Duration open:', data.duration)
end)

-- Context menu item hovered event
AddEventHandler('b2lib:context-item-hovered', function(data)
    print('Item hovered:', data.value)
    print('Context ID:', data.contextId)
    print('Item index:', data.index)
end)

-- Context menu position changed event
AddEventHandler('b2lib:context-position-changed', function(data)
    print('Context menu repositioned:', data.contextId)
    print('Old position:', json.encode(data.oldPosition))
    print('New position:', json.encode(data.newPosition))
    print('Reason:', data.reason) -- 'boundary-adjustment', 'manual'
end)

-- Context menu updated event
AddEventHandler('b2lib:context-updated', function(data)
    print('Context menu updated:', data.contextId)
    print('Update type:', data.updateType) -- 'items', 'data', 'style'
    print('Changes:', json.encode(data.changes))
end)

Server Events

-- Player context menu shown event
AddEventHandler('b2lib:context-player-shown', function(playerId, contextId, contextData)
    print('Context menu shown to player', playerId, ':', contextId)
    
    -- Log admin context menu usage
    if contextId:find('admin') then
        print('Admin context menu shown to player', playerId)
        -- Log to admin activity system
    end
end)

-- Player context menu item selected event
AddEventHandler('b2lib:context-player-selected', function(playerId, contextId, itemValue, contextData)
    print('Player', playerId, 'selected item:', itemValue, 'from context:', contextId)
    
    -- Handle server-side context actions
    if contextId == 'admin_context' and itemValue == 'ban' then
        -- Additional server-side validation for ban action
        if IsPlayerAceAllowed(playerId, 'admin.ban') then
            print('Ban action authorized for player', playerId)
        else
            print('Unauthorized ban attempt by player', playerId)
        end
    end
end)

-- Player context menu closed event
AddEventHandler('b2lib:context-player-closed', function(playerId, contextId, reason)
    print('Context menu closed for player', playerId, ':', contextId, 'reason:', reason)
end)

Event Usage Examples

-- Comprehensive context menu analytics system
local ContextAnalytics = {
    stats = {
        menusShown = 0,
        itemsSelected = 0,
        menusClosed = 0,
        averageOpenTime = 0,
        popularItems = {},
        popularMenus = {},
        errorCount = 0
    },
    sessions = {}
}

function ContextAnalytics.trackEvent(eventType, data)
    ContextAnalytics.stats[eventType] = (ContextAnalytics.stats[eventType] or 0) + 1
    
    if eventType == 'menusShown' then
        -- Track menu popularity
        local contextId = data.contextId
        ContextAnalytics.stats.popularMenus[contextId] = (ContextAnalytics.stats.popularMenus[contextId] or 0) + 1
        
        -- Start session tracking
        ContextAnalytics.sessions[contextId] = {
            startTime = GetGameTimer(),
            contextData = data.contextData
        }
        
    elseif eventType == 'itemsSelected' then
        -- Track item popularity
        local itemValue = data.value
        ContextAnalytics.stats.popularItems[itemValue] = (ContextAnalytics.stats.popularItems[itemValue] or 0) + 1
        
    elseif eventType == 'menusClosed' then
        -- Calculate session duration
        local session = ContextAnalytics.sessions[data.contextId]
        if session then
            local duration = GetGameTimer() - session.startTime
            ContextAnalytics.stats.averageOpenTime = (ContextAnalytics.stats.averageOpenTime + duration) / 2
            ContextAnalytics.sessions[data.contextId] = nil
        end
    end
end

-- Event handlers
AddEventHandler('b2lib:context-opened', function(data)
    ContextAnalytics.trackEvent('menusShown', data)
    
    -- Auto-hide certain menus after timeout
    if data.contextId == 'temporary_context' then
        SetTimeout(5000, function()
            B2Lib.UI.hideContextMenu()
        end)
    end
    
    -- Log important context menu displays
    if data.contextId:find('admin') or data.contextId:find('sensitive') then
        print('Sensitive context menu opened:', data.contextId)
    end
end)

AddEventHandler('b2lib:context-item-selected', function(data)
    ContextAnalytics.trackEvent('itemsSelected', data)
    
    -- Handle global context actions
    if data.value == 'help' then
        TriggerEvent('help:showContextHelp', data.contextId)
    elseif data.value == 'report_bug' then
        TriggerEvent('bug:reportContext', data.contextId, data.contextData)
    end
    
    -- Validate dangerous actions
    if data.item.color == 'danger' then
        print('Dangerous action selected:', data.value, 'in context:', data.contextId)
    end
end)

AddEventHandler('b2lib:context-closed', function(data)
    ContextAnalytics.trackEvent('menusClosed', data)
    
    -- Clean up context-specific resources
    if data.contextId == 'vehicle_context' then
        -- Clean up vehicle interaction state
        TriggerEvent('vehicle:cleanupInteraction')
    elseif data.contextId == 'player_context' then
        -- Clean up player interaction state
        TriggerEvent('player:cleanupInteraction')
    end
end)

-- Command to view analytics
RegisterCommand('context-stats', function()
    print('=== Context Menu Analytics ===')
    print('Total menus shown:', ContextAnalytics.stats.menusShown)
    print('Total items selected:', ContextAnalytics.stats.itemsSelected)
    print('Average open time:', ContextAnalytics.stats.averageOpenTime .. 'ms')
    print('Most popular menu:', getMostPopular(ContextAnalytics.stats.popularMenus))
    print('Most popular item:', getMostPopular(ContextAnalytics.stats.popularItems))
end, false)

-- Context-aware menu management
local ContextManager = {
    activeContexts = {},
    maxConcurrentMenus = 3,
    priorities = {
        emergency = 10,
        admin = 8,
        player = 5,
        vehicle = 4,
        world = 2,
        inventory = 1
    }
}

function ContextManager.showPriorityContext(contextId, position, contextData, priority)
    priority = priority or ContextManager.priorities[contextId:match('(%w+)_')] or 1
    
    -- Check if we should override current context
    if #ContextManager.activeContexts >= ContextManager.maxConcurrentMenus then
        local lowestPriority = math.min(unpack(ContextManager.activeContexts, function(ctx) return ctx.priority end))
        if priority <= lowestPriority then
            return false -- Don't show lower priority context
        end
        
        -- Remove lowest priority context
        for i, ctx in ipairs(ContextManager.activeContexts) do
            if ctx.priority == lowestPriority then
                B2Lib.UI.hideContextMenu()
                table.remove(ContextManager.activeContexts, i)
                break
            end
        end
    end
    
    -- Show new context
    local success = B2Lib.UI.showContextMenu(contextId, position, contextData)
    if success then
        table.insert(ContextManager.activeContexts, {
            contextId = contextId,
            priority = priority,
            timestamp = GetGameTimer()
        })
    end
    
    return success
end

function ContextManager.hideContext(contextId)
    for i, ctx in ipairs(ContextManager.activeContexts) do
        if ctx.contextId == contextId then
            table.remove(ContextManager.activeContexts, i)
            break
        end
    end
    B2Lib.UI.hideContextMenu()
end

-- Example usage of priority context manager
AddEventHandler('emergency:showContext', function(position, contextData)
    ContextManager.showPriorityContext('emergency_context', position, contextData, 10)
end)

AddEventHandler('player:showContext', function(position, contextData)
    ContextManager.showPriorityContext('player_context', position, contextData, 5)
end)

Configuration

Complete Context Menu configuration options in config.lua:

--- Context Menu configuration
--- @type table
Config.ContextMenu = {
    maxWidth = 250,                     -- Default maximum width in pixels
    minWidth = 180,                     -- Default minimum width in pixels
    showIcons = true,                   -- Show icons by default
    showShortcuts = true,               -- Show keyboard shortcuts by default
    autoHide = true,                    -- Auto-hide on click outside menu area
    autoClose = true,                   -- Auto-close after item selection
    animationDuration = 200,            -- Animation duration in milliseconds
    showOnRightClick = true,            -- Show on right-click events automatically
    closeOnSelection = true,            -- Close menu after item selection
    maxItems = 15,                      -- Maximum number of items before scrolling
    zIndex = 1000,                      -- Default z-index for menu layering
    boundaryPadding = 10,               -- Padding from screen edges in pixels
    submenuDelay = 300,                 -- Delay before showing submenus in milliseconds
    tooltipDelay = 500                  -- Delay before showing tooltips in milliseconds
}

-- Context menu styling configuration
Config.ComponentStyles.contextMenu = {
    minWidth = '180px',                 -- Minimum menu width
    maxWidth = '250px',                 -- Maximum menu width
    maxHeight = '400px',                -- Maximum menu height before scrolling
    padding = '8px 0',                  -- Menu container padding
    borderRadius = 'lg',                -- Border radius (from Config.Theme.borderRadius)
    shadow = 'xl',                      -- Shadow style (from Config.Theme.shadows)
    borderWidth = '1px',                -- Border width
    backdropBlur = '4px',               -- Background blur effect
    
    -- Title styling
    title = {
        padding = '12px 16px 8px 16px',  -- Title padding
        fontSize = 'sm',                 -- Title font size
        fontWeight = 'semibold',         -- Title font weight
        borderBottom = '1px',            -- Title border bottom
        marginBottom = '4px'             -- Title margin bottom
    },
    
    -- Item styling
    item = {
        padding = '8px 16px',            -- Item padding
        fontSize = 'sm',                 -- Item font size
        fontWeight = 'medium',           -- Item font weight
        hoverTransition = 'fast',        -- Hover transition speed
        iconSize = '16px',               -- Icon size
        iconMargin = '8px',              -- Icon margin from text
        shortcutFontSize = 'xs',         -- Shortcut font size
        shortcutOpacity = '0.6',         -- Shortcut opacity
        badgeSize = '18px',              -- Badge size
        badgeMargin = '4px',             -- Badge margin
        minHeight = '36px'               -- Minimum item height
    },
    
    -- Separator styling
    separator = {
        height = '1px',                  -- Separator height
        margin = '4px 0',                -- Separator margin
        opacity = '0.2',                 -- Separator opacity
        backgroundColor = 'border'       -- Separator background color
    },
    
    -- Submenu styling
    submenu = {
        arrowSize = '12px',              -- Submenu arrow size
        arrowMargin = '8px',             -- Submenu arrow margin
        offset = '4px'                   -- Submenu position offset
    },
    
    -- Scrollbar styling
    scrollbar = {
        width = '6px',                   -- Scrollbar width
        trackColor = 'transparent',      -- Scrollbar track color
        thumbColor = 'rgba(0,0,0,0.3)',  -- Scrollbar thumb color
        hoverThumbColor = 'rgba(0,0,0,0.5)' -- Scrollbar thumb hover color
    },
    
    -- Animation settings
    animations = {
        fadeIn = 'fadeIn 0.2s ease-out',
        fadeOut = 'fadeOut 0.15s ease-in',
        slideIn = 'slideIn 0.2s ease-out',
        slideOut = 'slideOut 0.15s ease-in',
        scaleIn = 'scaleIn 0.2s ease-out',
        scaleOut = 'scaleOut 0.15s ease-in'
    },
    
    -- Color variants
    colors = {
        default = 'text-gray-700 dark:text-gray-300',      -- Default item color
        danger = 'text-red-600 dark:text-red-400',         -- Danger item color
        warning = 'text-yellow-600 dark:text-yellow-400',  -- Warning item color
        success = 'text-green-600 dark:text-green-400',    -- Success item color
        info = 'text-blue-600 dark:text-blue-400'          -- Info item color
    },
    
    -- Badge colors
    badgeColors = {
        default = 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200',
        danger = 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
        warning = 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
        success = 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
        info = 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
    }
}

Advanced Examples

Comprehensive Player Interaction System

-- Create a sophisticated player interaction system with dynamic menus
local PlayerInteraction = {}
PlayerInteraction.activeTarget = nil
PlayerInteraction.menuVisible = false

function PlayerInteraction.init()
    -- Register base player context menu
    B2Lib.UI.registerContextMenu('player_interaction', {
        title = 'Player Actions',
        maxWidth = 280,
        showShortcuts = true,
        items = {
            { label = 'Send Message', value = 'message', icon = 'message-circle', shortcut = 'M' },
            { label = 'Add Friend', value = 'friend', icon = 'user-plus', shortcut = 'F' },
            { label = 'View Profile', value = 'profile', icon = 'user', shortcut = 'P' },
            { separator = true },
            { label = 'Trade Request', value = 'trade', icon = 'handshake', 
              condition = function(ctx) return ctx.distance < 5.0 end },
            { label = 'Give Money', value = 'give_money', icon = 'dollar-sign',
              condition = function(ctx) return ctx.playerMoney > 0 end },
            { label = 'Share Location', value = 'share_location', icon = 'map-pin' },
            { separator = true },
            { label = 'Admin Actions', value = 'admin_submenu', icon = 'shield', submenu = 'admin_actions',
              condition = function(ctx) return ctx.isAdmin end, color = 'warning' },
            { separator = true },
            { label = 'Report Player', value = 'report', icon = 'flag', color = 'danger', shortcut = 'R' }
        }
    })
    
    -- Register admin submenu
    B2Lib.UI.registerContextMenu('admin_actions', {
        title = 'Admin Actions',
        items = {
            { label = 'Teleport To Player', value = 'teleport_to', icon = 'zap' },
            { label = 'Bring Player', value = 'bring_player', icon = 'move' },
            { label = 'Spectate Player', value = 'spectate', icon = 'eye' },
            { separator = true },
            { label = 'Warn Player', value = 'warn', icon = 'alert-triangle', color = 'warning' },
            { label = 'Kick Player', value = 'kick', icon = 'user-x', color = 'danger' },
            { label = 'Ban Player', value = 'ban', icon = 'shield-off', color = 'danger' }
        }
    })
    
    PlayerInteraction.setupEventHandlers()
    PlayerInteraction.setupTargeting()
end

function PlayerInteraction.setupTargeting()
    CreateThread(function()
        while true do
            Wait(0)
            
            if IsControlJustPressed(0, 25) then -- Right mouse button
                local hit, coords, entity = GetCursorIntersection()
                
                if hit and IsEntityAPed(entity) and IsPedAPlayer(entity) then
                    local playerId = NetworkGetPlayerIndexFromPed(entity)
                    local playerName = GetPlayerName(playerId)
                    
                    if playerId ~= PlayerId() and playerId ~= -1 then
                        PlayerInteraction.showPlayerMenu(playerId, playerName, entity)
                    end
                end
            end
            
            -- Hide menu if target is too far
            if PlayerInteraction.menuVisible and PlayerInteraction.activeTarget then
                local distance = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(PlayerInteraction.activeTarget.ped))
                if distance > 15.0 then
                    B2Lib.UI.hideContextMenu()
                    PlayerInteraction.menuVisible = false
                    PlayerInteraction.activeTarget = nil
                end
            end
        end
    end)
end

function PlayerInteraction.showPlayerMenu(playerId, playerName, playerPed)
    local mouseX, mouseY = GetNuiCursorPosition()
    local playerCoords = GetEntityCoords(PlayerPedId())
    local targetCoords = GetEntityCoords(playerPed)
    local distance = #(playerCoords - targetCoords)
    
    PlayerInteraction.activeTarget = {
        id = playerId,
        name = playerName,
        ped = playerPed,
        coords = targetCoords
    }
    
    local contextData = {
        playerId = playerId,
        playerName = playerName,
        playerPed = playerPed,
        distance = distance,
        playerMoney = getPlayerMoney(), -- Custom function
        isAdmin = isPlayerAdmin(), -- Custom function
        isFriend = isPlayerFriend(playerId), -- Custom function
        canTrade = distance < 5.0 and not isPlayerBusy(playerId) -- Custom function
    }
    
    B2Lib.UI.showContextMenu('player_interaction', {
        x = mouseX,
        y = mouseY,
        anchor = 'top-left'
    }, contextData)
    
    PlayerInteraction.menuVisible = true
end

function PlayerInteraction.setupEventHandlers()
    AddEventHandler('b2lib:context-item-selected', function(data)
        if data.contextId == 'player_interaction' then
            PlayerInteraction.handlePlayerAction(data)
        elseif data.contextId == 'admin_actions' then
            PlayerInteraction.handleAdminAction(data)
        end
    end)
    
    AddEventHandler('b2lib:context-closed', function(contextId)
        if contextId == 'player_interaction' then
            PlayerInteraction.menuVisible = false
            PlayerInteraction.activeTarget = nil
        end
    end)
end

function PlayerInteraction.handlePlayerAction(data)
    local targetId = data.contextData.playerId
    local targetName = data.contextData.playerName
    
    if data.value == 'message' then
        B2Lib.UI.input({
            title = 'Send Message to ' .. targetName,
            placeholder = 'Enter your message...',
            maxLength = 128,
            icon = 'message-circle'
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            if inputData.value and inputData.value ~= '' then
                TriggerServerEvent('chat:sendPrivateMessage', targetId, inputData.value)
                B2Lib.UI.notify({
                    type = 'success',
                    message = 'Message sent to ' .. targetName,
                    icon = 'check'
                })
            end
        end)
        
    elseif data.value == 'friend' then
        if not data.contextData.isFriend then
            TriggerServerEvent('friends:sendRequest', targetId)
            B2Lib.UI.notify({
                type = 'info',
                message = 'Friend request sent to ' .. targetName,
                icon = 'user-plus'
            })
        else
            B2Lib.UI.notify({
                type = 'warning',
                message = targetName .. ' is already your friend',
                icon = 'users'
            })
        end
        
    elseif data.value == 'trade' then
        if data.contextData.canTrade then
            TriggerServerEvent('trade:initiate', targetId)
            B2Lib.UI.notify({
                type = 'info',
                message = 'Trade request sent to ' .. targetName,
                icon = 'handshake'
            })
        else
            B2Lib.UI.notify({
                type = 'error',
                message = 'Cannot trade with ' .. targetName .. ' right now',
                icon = 'x-circle'
            })
        end
        
    elseif data.value == 'give_money' then
        B2Lib.UI.input({
            title = 'Give Money to ' .. targetName,
            placeholder = 'Enter amount...',
            type = 'number',
            min = 1,
            max = data.contextData.playerMoney,
            icon = 'dollar-sign'
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            local amount = tonumber(inputData.value)
            if amount and amount > 0 then
                TriggerServerEvent('money:give', targetId, amount)
            end
        end)
        
    elseif data.value == 'share_location' then
        local coords = GetEntityCoords(PlayerPedId())
        TriggerServerEvent('location:share', targetId, coords)
        B2Lib.UI.notify({
            type = 'success',
            message = 'Location shared with ' .. targetName,
            icon = 'map-pin'
        })
        
    elseif data.value == 'report' then
        B2Lib.UI.input({
            title = 'Report ' .. targetName,
            placeholder = 'Enter reason for report...',
            maxLength = 256,
            icon = 'flag'
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            if inputData.value and inputData.value ~= '' then
                TriggerServerEvent('admin:reportPlayer', targetId, inputData.value)
                B2Lib.UI.notify({
                    type = 'success',
                    message = 'Report submitted',
                    icon = 'check'
                })
            end
        end)
    end
end

function PlayerInteraction.handleAdminAction(data)
    local targetId = data.contextData.playerId
    local targetName = data.contextData.playerName
    
    if data.value == 'teleport_to' then
        TriggerServerEvent('admin:teleportToPlayer', targetId)
    elseif data.value == 'bring_player' then
        TriggerServerEvent('admin:bringPlayer', targetId)
    elseif data.value == 'spectate' then
        TriggerServerEvent('admin:spectatePlayer', targetId)
    elseif data.value == 'warn' then
        B2Lib.UI.input({
            title = 'Warn ' .. targetName,
            placeholder = 'Enter warning reason...',
            maxLength = 128
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            if inputData.value and inputData.value ~= '' then
                TriggerServerEvent('admin:warnPlayer', targetId, inputData.value)
            end
        end)
    elseif data.value == 'kick' then
        TriggerServerEvent('admin:kickPlayer', targetId)
    elseif data.value == 'ban' then
        B2Lib.UI.input({
            title = 'Ban ' .. targetName,
            placeholder = 'Enter ban reason...',
            maxLength = 256
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            if inputData.value and inputData.value ~= '' then
                TriggerServerEvent('admin:banPlayer', targetId, inputData.value)
            end
        end)
    end
end

-- Initialize the system
PlayerInteraction.init()

Advanced Vehicle Context System

-- Create a comprehensive vehicle interaction system with state management
local VehicleContext = {}
VehicleContext.activeVehicle = nil
VehicleContext.updateInterval = 500

function VehicleContext.init()
    -- Register vehicle context menu
    B2Lib.UI.registerContextMenu('vehicle_context', {
        title = 'Vehicle Options',
        maxWidth = 300,
        items = {
            { label = 'Enter as Driver', value = 'enter_driver', icon = 'car',
              condition = function(ctx) return not ctx.isLocked or ctx.hasKeys end },
            { label = 'Enter as Passenger', value = 'enter_passenger', icon = 'users',
              condition = function(ctx) return ctx.hasPassengerSeats and (not ctx.isLocked or ctx.hasKeys) end },
            { separator = true },
            { label = 'Lock Vehicle', value = 'lock', icon = 'lock',
              condition = function(ctx) return ctx.hasKeys and not ctx.isLocked end },
            { label = 'Unlock Vehicle', value = 'unlock', icon = 'unlock',
              condition = function(ctx) return ctx.hasKeys and ctx.isLocked end },
            { separator = true },
            { label = 'Open Hood', value = 'hood', icon = 'wrench',
              condition = function(ctx) return not ctx.isLocked or ctx.hasKeys end },
            { label = 'Open Trunk', value = 'trunk', icon = 'package',
              condition = function(ctx) return not ctx.isLocked or ctx.hasKeys end },
            { label = 'Check Engine', value = 'engine_check', icon = 'settings',
              condition = function(ctx) return ctx.hasKeys end },
            { separator = true },
            { label = 'Steal Vehicle', value = 'steal', icon = 'key', color = 'warning',
              condition = function(ctx) return not ctx.hasKeys and ctx.canSteal end },
            { label = 'Break Window', value = 'break_window', icon = 'shield-off', color = 'danger',
              condition = function(ctx) return ctx.isLocked and not ctx.hasKeys end },
            { separator = true },
            { label = 'Vehicle Info', value = 'info', icon = 'info' },
            { label = 'Call Mechanic', value = 'mechanic', icon = 'phone',
              condition = function(ctx) return ctx.health < 50 end }
        }
    })
    
    VehicleContext.setupTargeting()
    VehicleContext.setupEventHandlers()
end

function VehicleContext.setupTargeting()
    CreateThread(function()
        while true do
            Wait(0)
            
            if IsControlJustPressed(0, 25) then -- Right mouse button
                local hit, coords, entity = GetCursorIntersection()
                
                if hit and IsEntityAVehicle(entity) then
                    VehicleContext.showVehicleMenu(entity)
                end
            end
        end
    end)
    
    -- Update vehicle state periodically
    CreateThread(function()
        while true do
            Wait(VehicleContext.updateInterval)
            
            if VehicleContext.activeVehicle and B2Lib.UI.isContextMenuVisible() then
                local contextData = VehicleContext.getVehicleData(VehicleContext.activeVehicle)
                B2Lib.UI.updateContextData(contextData)
            end
        end
    end)
end

function VehicleContext.getVehicleData(vehicle)
    if not DoesEntityExist(vehicle) then return nil end
    
    local playerPed = PlayerPedId()
    local vehicleCoords = GetEntityCoords(vehicle)
    local playerCoords = GetEntityCoords(playerPed)
    local distance = #(playerCoords - vehicleCoords)
    
    local lockStatus = GetVehicleDoorLockStatus(vehicle)
    local isLocked = lockStatus == 2
    local health = GetEntityHealth(vehicle)
    local maxHealth = GetEntityMaxHealth(vehicle)
    local healthPercent = (health / maxHealth) * 100
    
    return {
        vehicle = vehicle,
        distance = distance,
        isLocked = isLocked,
        hasKeys = hasVehicleKeys(vehicle), -- Custom function
        canSteal = hasLockpicks() and not isPoliceVehicle(vehicle), -- Custom function
        hasPassengerSeats = GetVehicleMaxNumberOfPassengers(vehicle) > 0,
        health = healthPercent,
        fuel = GetVehicleFuelLevel(vehicle),
        model = GetEntityModel(vehicle),
        plate = GetVehicleNumberPlateText(vehicle),
        displayName = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
    }
end

function VehicleContext.showVehicleMenu(vehicle)
    local mouseX, mouseY = GetNuiCursorPosition()
    local contextData = VehicleContext.getVehicleData(vehicle)
    
    if not contextData then return end
    
    VehicleContext.activeVehicle = vehicle
    
    B2Lib.UI.showContextMenu('vehicle_context', {
        x = mouseX,
        y = mouseY,
        anchor = 'top-left'
    }, contextData)
end

function VehicleContext.setupEventHandlers()
    AddEventHandler('b2lib:context-item-selected', function(data)
        if data.contextId == 'vehicle_context' then
            VehicleContext.handleVehicleAction(data)
        end
    end)
    
    AddEventHandler('b2lib:context-closed', function(contextId)
        if contextId == 'vehicle_context' then
            VehicleContext.activeVehicle = nil
        end
    end)
end

function VehicleContext.handleVehicleAction(data)
    local vehicle = data.contextData.vehicle
    local playerPed = PlayerPedId()
    
    if data.value == 'enter_driver' then
        TaskEnterVehicle(playerPed, vehicle, -1, -1, 1.0, 1, 0)
        
    elseif data.value == 'enter_passenger' then
        local seat = getAvailablePassengerSeat(vehicle) -- Custom function
        if seat then
            TaskEnterVehicle(playerPed, vehicle, -1, seat, 1.0, 1, 0)
        end
        
    elseif data.value == 'lock' or data.value == 'unlock' then
        local newLockState = data.value == 'lock' and 2 or 1
        SetVehicleDoorsLocked(vehicle, newLockState)
        
        B2Lib.UI.notify({
            type = 'success',
            message = 'Vehicle ' .. data.value .. 'ed',
            icon = data.value == 'lock' and 'lock' or 'unlock'
        })
        
    elseif data.value == 'hood' then
        SetVehicleDoorOpen(vehicle, 4, false, false) -- Hood
        
    elseif data.value == 'trunk' then
        SetVehicleDoorOpen(vehicle, 5, false, false) -- Trunk
        
    elseif data.value == 'engine_check' then
        local engineHealth = GetVehicleEngineHealth(vehicle)
        local bodyHealth = GetVehicleBodyHealth(vehicle)
        
        B2Lib.UI.notify({
            type = 'info',
            message = string.format('Engine: %.0f%% | Body: %.0f%%', engineHealth/10, bodyHealth/10),
            icon = 'settings',
            duration = 5000
        })
        
    elseif data.value == 'steal' then
        -- Start lockpicking minigame
        B2Lib.UI.skillcheck({
            difficulty = 'medium',
            keys = {'q', 'w', 'e', 'r'},
            duration = 8000
        })
        
        AddEventHandler('b2lib:skillcheck-result', function(result)
            if result.success then
                SetVehicleDoorsLocked(vehicle, 1)
                B2Lib.UI.notify({
                    type = 'success',
                    message = 'Vehicle unlocked!',
                    icon = 'key'
                })
            else
                B2Lib.UI.notify({
                    type = 'error',
                    message = 'Lockpicking failed',
                    icon = 'x-circle'
                })
            end
        end)
        
    elseif data.value == 'break_window' then
        -- Break window animation and sound
        SetVehicleDoorBroken(vehicle, 0, true) -- Driver door window
        B2Lib.UI.notify({
            type = 'warning',
            message = 'Window broken!',
            icon = 'shield-off'
        })
        
    elseif data.value == 'info' then
        local info = string.format(
            'Vehicle: %s\nPlate: %s\nHealth: %.0f%%\nFuel: %.0f%%',
            data.contextData.displayName,
            data.contextData.plate,
            data.contextData.health,
            data.contextData.fuel
        )
        
        B2Lib.UI.alert({
            title = 'Vehicle Information',
            message = info,
            icon = 'info'
        })
        
    elseif data.value == 'mechanic' then
        TriggerServerEvent('mechanic:callForVehicle', NetworkGetNetworkIdFromEntity(vehicle))
        B2Lib.UI.notify({
            type = 'info',
            message = 'Mechanic called for vehicle repair',
            icon = 'phone'
        })
    end
end

-- Initialize the system
VehicleContext.init()

Dynamic Inventory Context Menu

-- Create a dynamic inventory context menu system
local InventoryContext = {}
InventoryContext.currentItem = nil

function InventoryContext.init()
    -- Register inventory item context menu
    B2Lib.UI.registerContextMenu('inventory_item', {
        title = 'Item Actions',
        maxWidth = 250,
        items = {
            { label = 'Use Item', value = 'use', icon = 'play',
              condition = function(ctx) return ctx.item.usable end },
            { label = 'Examine', value = 'examine', icon = 'search' },
            { separator = true },
            { label = 'Drop Item', value = 'drop', icon = 'package' },
            { label = 'Give to Player', value = 'give', icon = 'gift',
              condition = function(ctx) return ctx.nearbyPlayers > 0 end },
            { label = 'Split Stack', value = 'split', icon = 'scissors',
              condition = function(ctx) return ctx.item.quantity > 1 end },
            { separator = true },
            { label = 'Favorite', value = 'favorite', icon = 'star',
              condition = function(ctx) return not ctx.item.favorited end },
            { label = 'Unfavorite', value = 'unfavorite', icon = 'star-off',
              condition = function(ctx) return ctx.item.favorited end },
            { separator = true },
            { label = 'Destroy Item', value = 'destroy', icon = 'trash-2', color = 'danger' }
        }
    })
    
    InventoryContext.setupEventHandlers()
end

function InventoryContext.showItemMenu(item, position)
    local nearbyPlayers = getNearbyPlayers(5.0) -- Custom function
    
    InventoryContext.currentItem = item
    
    B2Lib.UI.showContextMenu('inventory_item', position, {
        item = item,
        nearbyPlayers = #nearbyPlayers,
        playerInventory = getPlayerInventory() -- Custom function
    })
end

function InventoryContext.setupEventHandlers()
    AddEventHandler('b2lib:context-item-selected', function(data)
        if data.contextId == 'inventory_item' then
            InventoryContext.handleItemAction(data)
        end
    end)
end

function InventoryContext.handleItemAction(data)
    local item = data.contextData.item
    
    if data.value == 'use' then
        TriggerServerEvent('inventory:useItem', item.id, item.slot)
        
    elseif data.value == 'examine' then
        B2Lib.UI.alert({
            title = item.label,
            message = item.description or 'No description available.',
            icon = 'search'
        })
        
    elseif data.value == 'drop' then
        B2Lib.UI.input({
            title = 'Drop ' .. item.label,
            placeholder = 'Enter quantity to drop...',
            type = 'number',
            min = 1,
            max = item.quantity,
            defaultValue = item.quantity
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            local quantity = tonumber(inputData.value) or item.quantity
            TriggerServerEvent('inventory:dropItem', item.id, item.slot, quantity)
        end)
        
    elseif data.value == 'give' then
        -- Show nearby players selection
        local nearbyPlayers = getNearbyPlayers(5.0)
        local playerItems = {}
        
        for _, player in ipairs(nearbyPlayers) do
            table.insert(playerItems, {
                label = GetPlayerName(player.id),
                value = 'give_' .. player.id,
                icon = 'user'
            })
        end
        
        B2Lib.UI.registerContextMenu('give_players', {
            title = 'Give to Player',
            items = playerItems
        })
        
        B2Lib.UI.showContextMenu('give_players', {
            x = 400,
            y = 300
        }, { item = item })
        
    elseif data.value == 'split' then
        B2Lib.UI.input({
            title = 'Split ' .. item.label,
            placeholder = 'Enter quantity to split...',
            type = 'number',
            min = 1,
            max = item.quantity - 1
        })
        
        AddEventHandler('b2lib:input-submitted', function(inputData)
            local quantity = tonumber(inputData.value)
            if quantity then
                TriggerServerEvent('inventory:splitItem', item.id, item.slot, quantity)
            end
        end)
        
    elseif data.value == 'favorite' or data.value == 'unfavorite' then
        TriggerServerEvent('inventory:toggleFavorite', item.id, item.slot)
        
    elseif data.value == 'destroy' then
        B2Lib.UI.confirm({
            title = 'Destroy Item',
            message = 'Are you sure you want to destroy ' .. item.label .. '? This action cannot be undone.',
            confirmText = 'Destroy',
            cancelText = 'Cancel'
        })
        
        AddEventHandler('b2lib:confirm-result', function(result)
            if result.confirmed then
                TriggerServerEvent('inventory:destroyItem', item.id, item.slot)
            end
        end)
    end
end

-- Initialize the system
InventoryContext.init()

Vehicle Context Menu System

-- Create a vehicle interaction context menu
local function setupVehicleContextMenu()
    B2Lib.UI.registerContextMenu('vehicle_interaction', {
        title = 'Vehicle Options',
        items = {
            { label = 'Enter as Driver', value = 'enter_driver', icon = 'car' },
            { label = 'Enter as Passenger', value = 'enter_passenger', icon = 'users' },
            { separator = true },
            { label = 'Lock/Unlock', value = 'toggle_lock', icon = 'lock' },
            { label = 'Open Hood', value = 'open_hood', icon = 'wrench' },
            { label = 'Open Trunk', value = 'open_trunk', icon = 'package' },
            { separator = true },
            { label = 'Steal Vehicle', value = 'steal', icon = 'key', color = 'warning' },
            { label = 'Break Window', value = 'break_window', icon = 'shield-off', color = 'danger' }
        }
    })
    
    -- Vehicle targeting system
    CreateThread(function()
        while true do
            Wait(0)
            
            if IsControlJustPressed(0, 25) then -- Right mouse button
                local hit, coords, entity = GetCursorIntersection()
                
                if hit and IsEntityAVehicle(entity) then
                    local playerPed = PlayerPedId()
                    local vehicleCoords = GetEntityCoords(entity)
                    local playerCoords = GetEntityCoords(playerPed)
                    local distance = #(playerCoords - vehicleCoords)
                    
                    if distance < 10.0 then
                        local mouseX, mouseY = GetNuiCursorPosition()
                        local lockStatus = GetVehicleDoorLockStatus(entity)
                        local isLocked = lockStatus == 2
                        
                        -- Update menu items based on vehicle state
                        local contextData = {
                            vehicle = entity,
                            distance = distance,
                            isLocked = isLocked,
                            model = GetEntityModel(entity),
                            plate = GetVehicleNumberPlateText(entity),
                            hasKeys = hasVehicleKeys(entity), -- Custom function
                            canSteal = hasLockpicks() -- Custom function
                        }
                        
                        B2Lib.UI.showContextMenu('vehicle_interaction', {
                            x = mouseX,
                            y = mouseY
                        }, contextData)
                    end
                end
            end
        end
    end)
    
    -- Handle vehicle context actions
    AddEventHandler('b2lib:context-item-selected', function(data)
        if data.contextId == 'vehicle_interaction' then
            local vehicle = data.contextData.vehicle
            local playerPed = PlayerPedId()
            
            if data.value == 'enter_driver' then
                if not data.contextData.isLocked or data.contextData.hasKeys then
                    TaskEnterVehicle(playerPed, vehicle, -1, -1, 1.0, 1, 0)
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'Vehicle is locked'
                    })
                end
                
            elseif data.value == 'enter_passenger' then
                local seat = getAvailablePassengerSeat(vehicle) -- Custom function
                if seat and (not data.contextData.isLocked or data.contextData.hasKeys) then
                    TaskEnterVehicle(playerPed, vehicle, -1, seat, 1.0, 1, 0)
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'No available seats or vehicle is locked'
                    })
                end
                
            elseif data.value == 'toggle_lock' then
                if data.contextData.hasKeys then
                    local newLockState = data.contextData.isLocked and 1 or 2
                    SetVehicleDoorsLocked(vehicle, newLockState)
                    B2Lib.UI.notify({
                        type = 'success',
                        message = 'Vehicle ' .. (data.contextData.isLocked and 'unlocked' or 'locked')
                    })
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'You don\'t have keys to this vehicle'
                    })
                end
                
            elseif data.value == 'steal' then
                if data.contextData.canSteal then
                    -- Start lockpicking minigame
                    B2Lib.UI.lockpicking({
                        difficulty = 'medium',
                        duration = 10000
                    })
                    
                    AddEventHandler('b2lib:lockpicking-result', function(result)
                        if result.success then
                            SetVehicleDoorsLocked(vehicle, 1)
                            B2Lib.UI.notify({
                                type = 'success',
                                message = 'Vehicle unlocked!'
                            })
                        else
                            B2Lib.UI.notify({
                                type = 'error',
                                message = 'Lockpicking failed'
                            })
                        end
                    end)
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'You need lockpicks to steal this vehicle'
                    })
                end
            end
        end
    end)
end

World Interaction Context Menu

-- Create a world interaction context menu
local function setupWorldContextMenu()
    B2Lib.UI.registerContextMenu('world_interaction', {
        items = {
            { label = 'Drop Item', value = 'drop_item', icon = 'package' },
            { label = 'Place Waypoint', value = 'waypoint', icon = 'map-pin' },
            { label = 'Take Screenshot', value = 'screenshot', icon = 'camera' },
            { separator = true },
            { label = 'Spawn Vehicle', value = 'spawn_vehicle', icon = 'car', color = 'warning' },
            { label = 'Teleport Here', value = 'teleport', icon = 'zap', color = 'danger' }
        }
    })
    
    -- World right-click handler
    CreateThread(function()
        while true do
            Wait(0)
            
            if IsControlJustPressed(0, 25) then -- Right mouse button
                local hit, coords, entity = GetCursorIntersection()
                
                -- Only show if clicking on world (not entities)
                if hit and entity == 0 then
                    local mouseX, mouseY = GetNuiCursorPosition()
                    
                    B2Lib.UI.showContextMenu('world_interaction', {
                        x = mouseX,
                        y = mouseY
                    }, {
                        worldCoords = coords,
                        hasItems = #getPlayerInventory() > 0, -- Custom function
                        isAdmin = isPlayerAdmin() -- Custom function
                    })
                end
            end
        end
    end)
    
    -- Handle world context actions
    AddEventHandler('b2lib:context-item-selected', function(data)
        if data.contextId == 'world_interaction' then
            local coords = data.contextData.worldCoords
            
            if data.value == 'drop_item' then
                if data.contextData.hasItems then
                    -- Show inventory menu to select item to drop
                    showInventoryDropMenu(coords)
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'You have no items to drop'
                    })
                end
                
            elseif data.value == 'waypoint' then
                SetNewWaypoint(coords.x, coords.y)
                B2Lib.UI.notify({
                    type = 'success',
                    message = 'Waypoint set'
                })
                
            elseif data.value == 'screenshot' then
                exports['screenshot-basic']:requestScreenshotUpload('https://example.com/upload', 'files[]', function(data)
                    B2Lib.UI.notify({
                        type = 'success',
                        message = 'Screenshot saved'
                    })
                end)
                
            elseif data.value == 'spawn_vehicle' then
                if data.contextData.isAdmin then
                    -- Show vehicle spawn menu
                    showVehicleSpawnMenu(coords)
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'Insufficient permissions'
                    })
                end
                
            elseif data.value == 'teleport' then
                if data.contextData.isAdmin then
                    SetEntityCoords(PlayerPedId(), coords.x, coords.y, coords.z + 1.0)
                    B2Lib.UI.notify({
                        type = 'success',
                        message = 'Teleported to location'
                    })
                else
                    B2Lib.UI.notify({
                        type = 'error',
                        message = 'Insufficient permissions'
                    })
                end
            end
        end
    end)
end

Best Practices

  1. Register Menus Early - Register all context menus during resource startup to ensure availability when needed

  2. Use Descriptive IDs - Choose meaningful, consistent context menu identifiers that reflect their purpose and scope

  3. Implement Smart Positioning - Consider screen boundaries, menu size, and user interface elements when positioning menus

  4. Provide Rich Context Data - Pass comprehensive, relevant information to menu handlers for dynamic behavior

  5. Leverage Visual Hierarchy - Use icons, colors, separators, and grouping to create clear visual organization

  6. Validate Permissions Dynamically - Use condition functions to show/hide items based on real-time permission checks

  7. Handle State Changes - Update context data and menu items when underlying conditions change

  8. Optimize Performance - Use reasonable update intervals and avoid excessive menu registrations or data processing

  9. Implement Proper Cleanup - Clean up event handlers, timers, and resources when menus are closed or no longer needed

  10. Use Consistent Styling - Maintain visual consistency across different context menus in your resource

  11. Provide Keyboard Shortcuts - Include keyboard shortcuts for frequently used actions to improve accessibility

  12. Test Edge Cases - Verify menu behavior with invalid entities, disconnected players, and boundary conditions

  13. Implement Error Handling - Add proper error handling for failed actions and invalid context data

  14. Use Submenu Strategically - Organize complex actions into logical submenu structures without overwhelming users

  15. Consider Mobile Compatibility - Ensure context menus work well on different screen sizes and input methods

Troubleshooting

Context Menu Not Showing

  1. Registration Issues

    • Verify the context menu is registered before attempting to show it

    • Check that the contextId matches exactly between registration and show calls

    • Ensure registration occurs during resource startup or before first use

  2. Position Problems

    • Verify position coordinates are valid numbers within screen bounds

    • Check that x/y values are positive and not NaN or nil

    • Test with fixed coordinates (e.g., {x: 400, y: 300}) before using dynamic positioning

  3. Blocking Elements

    • Ensure no other modal dialogs or UI elements are blocking the display

    • Check for conflicting z-index values with other UI components

    • Verify the menu isn't being hidden immediately after being shown

  4. Debug Information

    • Check console for error messages from B2Lib.Debug

    • Enable debug mode to see detailed context menu operations

    • Verify B2Lib is properly initialized and loaded

  1. Boundary Detection

    • Implement boundary padding to prevent menus from appearing outside screen edges

    • Use anchor points to control how menus position relative to coordinates

    • Test positioning on different screen resolutions and aspect ratios

  2. Dynamic Positioning

    • Consider using GetNuiCursorPosition() for mouse-based positioning

    • Account for menu dimensions when calculating optimal position

    • Implement fallback positioning for edge cases

  3. Multi-Monitor Setup

    • Test context menu positioning on multi-monitor configurations

    • Ensure menus appear on the correct monitor

    • Handle negative coordinates appropriately

Context Data Not Working

  1. Data Structure

    • Ensure context data is passed as a properly structured table

    • Verify that event handlers access data.contextData correctly

    • Check for typos in context data property names

  2. Data Validation

    • Implement validation for critical context data properties

    • Handle cases where expected data is missing or invalid

    • Use default values for optional context data

  3. Dynamic Updates

    • Verify that updateContextData() is called when underlying data changes

    • Check that condition functions receive updated context data

    • Ensure event handlers can access the latest context information

Performance Issues

  1. Menu Frequency

    • Avoid showing/hiding context menus too frequently (< 100ms intervals)

    • Implement debouncing for rapid menu operations

    • Use reasonable update intervals for dynamic context data

  2. Resource Management

    • Clean up event handlers when they're no longer needed

    • Limit the number of registered context menus

    • Optimize context data calculations and avoid expensive operations

  3. Item Count Optimization

    • Use reasonable item counts in context menus (< 15 items recommended)

    • Implement scrolling for menus with many items

    • Consider using submenus to organize large sets of actions

  4. Memory Leaks

    • Properly clean up context menu references and event handlers

    • Avoid storing large objects in context data

    • Monitor memory usage during extended context menu usage

Condition Function Errors

  1. Function Validation

    • Ensure condition functions return boolean values

    • Handle errors within condition functions gracefully

    • Test condition functions with various context data scenarios

  2. Performance Optimization

    • Keep condition functions lightweight and fast

    • Avoid expensive operations in condition functions

    • Cache results when appropriate to improve performance

  3. Error Handling

    • Implement try-catch blocks in condition functions

    • Provide fallback behavior when condition functions fail

    • Log condition function errors for debugging

Server-Side Issues

  1. Player Validation

    • Verify player exists and is connected before showing server-side context menus

    • Handle player disconnections gracefully

    • Implement proper permission checks on the server side

  2. Network Synchronization

    • Account for network latency when showing context menus to players

    • Implement proper error handling for failed network operations

    • Use appropriate timeouts for server-side context menu operations

  3. Resource Conflicts

    • Check for conflicts with other resources using context menus

    • Ensure proper resource dependencies are declared

    • Test context menu functionality after resource restarts

Last updated