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.

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 managementmenuData
(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 tocontextId
(string, required): Registered context menu identifier to displayposition
(table, required): Position coordinates with smart boundary detectionx
(number): X coordinate in pixels from left edge of screeny
(number): Y coordinate in pixels from top edge of screenanchor
(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 identifierposition
(table): Current menu position coordinatescontextData
(table): Current context dataitemCount
(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 updatemenuData
(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
Register Menus Early - Register all context menus during resource startup to ensure availability when needed
Use Descriptive IDs - Choose meaningful, consistent context menu identifiers that reflect their purpose and scope
Implement Smart Positioning - Consider screen boundaries, menu size, and user interface elements when positioning menus
Provide Rich Context Data - Pass comprehensive, relevant information to menu handlers for dynamic behavior
Leverage Visual Hierarchy - Use icons, colors, separators, and grouping to create clear visual organization
Validate Permissions Dynamically - Use condition functions to show/hide items based on real-time permission checks
Handle State Changes - Update context data and menu items when underlying conditions change
Optimize Performance - Use reasonable update intervals and avoid excessive menu registrations or data processing
Implement Proper Cleanup - Clean up event handlers, timers, and resources when menus are closed or no longer needed
Use Consistent Styling - Maintain visual consistency across different context menus in your resource
Provide Keyboard Shortcuts - Include keyboard shortcuts for frequently used actions to improve accessibility
Test Edge Cases - Verify menu behavior with invalid entities, disconnected players, and boundary conditions
Implement Error Handling - Add proper error handling for failed actions and invalid context data
Use Submenu Strategically - Organize complex actions into logical submenu structures without overwhelming users
Consider Mobile Compatibility - Ensure context menus work well on different screen sizes and input methods
Troubleshooting
Context Menu Not Showing
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
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
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
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
Menu Positioning Issues
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
Dynamic Positioning
Consider using GetNuiCursorPosition() for mouse-based positioning
Account for menu dimensions when calculating optimal position
Implement fallback positioning for edge cases
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
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
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
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
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
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
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
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
Function Validation
Ensure condition functions return boolean values
Handle errors within condition functions gracefully
Test condition functions with various context data scenarios
Performance Optimization
Keep condition functions lightweight and fast
Avoid expensive operations in condition functions
Cache results when appropriate to improve performance
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
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
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
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