English version first. Scroll for Polish below.
onEvent
and sendEvent
.npm i fivem-ui-core
fivem-ui-core
.ThemeProvider
(default theme works out of the box, or pass your own):<script>
import { ThemeProvider, Notification, ProgressBar } from 'fivem-ui-core';
let hp = 75;
const theme = {
colors: {
primary: '#4CC9F0',
success: '#2ecc71',
warning: '#f39c12',
error: '#e74c3c',
surface: '#161616',
onSurface: '#ffffff',
border: '#2a2a2a'
},
radius: '10px',
fontFamily: 'Inter, system-ui, sans-serif'
};
</script>
<ThemeProvider {theme}>
<Notification type="success" message="Logged in successfully!" />
<ProgressBar value={hp} max={100} />
</ThemeProvider>
import { onEvent } from 'fivem-ui-core';
onEvent('showNotify', (data) => {
// { type: 'info' | 'success' | 'warning' | 'error', message: string }
console.log('Notification:', data);
});
import { sendEvent } from 'fivem-ui-core';
await sendEvent('callbackDone', { ok: true });
sendEvent(eventName, data)
— it POSTs to https://<resource>/<eventName>
under FiveM.Response { ok: true }
so development stays smooth.import { sendEvent } from 'fivem-ui-core';
await sendEvent('playerReady', { name: 'John' });
Need more resiliency? Use the client with built-in timeouts, retries (exponential backoff) and logging.
import { createNuiClient } from 'fivem-ui-core';
const client = createNuiClient({
timeoutMs: 5000, // default 5000
retries: 2, // default 2 attempts
backoffMs: 300, // default 300ms, grows exponentially per attempt
logger: (level, message, meta) => console[level]?.('[nui]', message, meta)
});
// raw Response
const res = await client.send('inventory:save', { items: [] });
if (!res.ok) throw new Error('Save error');
// auto JSON parse
const data = await client.sendJson('player:getProfile', { id: 123 });
Best practices:
sendJson
with response validation.Lua side (client.lua):
-- Register a callback with the same name
RegisterNUICallback('playerReady', function(data, cb)
print('UI said:', json.encode(data))
cb({ ok = true })
end)
SendNUIMessage
with action
and data
.action
via onEvent
.SendNUIMessage({
action = 'showNotify',
data = { type = 'info', message = 'New notification!' }
})
import { onEvent } from 'fivem-ui-core';
onEvent('showNotify', (data) => {
// e.g., show a styled toast based on type
});
SetNuiFocus(true, true)
so UI can receive input.SetNuiFocus(false, false)
.SendNUIMessage.action
↔ onEvent('<action>')
and sendEvent('<event>')
↔ RegisterNUICallback('<event>')
.ThemeProvider
.Theme keys:
colors.primary|success|warning|error|surface|onSurface|border
radius
— component border radiusfontFamily
— base fontMinimal example:
<script>
import { ThemeProvider, defaultTheme } from 'fivem-ui-core';
// copy defaultTheme and override only what you need
const theme = {
...defaultTheme,
colors: {
...defaultTheme.colors,
primary: '#00E5A8'
},
radius: '12px'
};
</script>
<ThemeProvider {theme}>
<slot />
</ThemeProvider>
presetLight
, presetDark
, and example serverPalettes
(red/blue/purple).import { presetLight, presetDark, serverPalettes } from 'fivem-ui-core';
// Light
const themeLight = presetLight;
// Dark with server palette "purple" as primary
const themeDarkPurple = {
...presetDark,
colors: {
...presetDark.colors,
primary: serverPalettes.purple.primary
}
};
type = 'info' | 'success' | 'warning' | 'error'
, message
, icon?
<Notification type="success" message="Operation completed" />
open
(bind), title
actions
<script>
let open = true;
</script>
<Modal bind:open title="Sample modal">
Modal content
<div slot="actions">
<button on:click={() => (open = false)}>Close</button>
</div>
</Modal>
value
, max = 100
, color?
label
(defaults to percentage)<ProgressBar value={75} />
<ProgressBar value={30} color="#ff4d4f" />
value
, max = 100
, size = 48
, stroke = 6
, color?
<ProgressCircle value={hp} max={100} size={64} />
hp
, hunger
, stamina
(0–100)<StatusBar hp={75} hunger={50} stamina={90} />
items: { slot: number; label?: string; icon?: string }[]
, selected?: number
<Hotbar items={[{ slot: 1, label: 'Pistol' }, { slot: 2, label: 'Medkit' }]} selected={1} />
open
, items: { id: string; label: string }[]
on:select={(e) => e.detail /* id */}
<RadialMenu
open
items={[{ id: 'anim', label: 'Animation' }, { id: 'veh', label: 'Vehicle' }]}
on:select={(e) => console.log('Selected:', e.detail)}
/>
items: { id; label; description? }[]
, selectedId?
, onSelect?
<script>
import { List } from 'fivem-ui-core';
let selected = null;
</script>
<List items={[{ id: 1, label: 'Option A' }, { id: 2, label: 'Option B', description: 'Desc' }]} onSelect={(id) => selected = id} />
value
, placeholder?
, label?
, type = 'text'|'password'|'number'
, disabled?
, name?
, id?
, onEnter?
<script>
import { TextInput } from 'fivem-ui-core';
let nick = '';
function handleEnter(v) { console.log('Enter:', v); }
</script>
<TextInput bind:value={nick} label="Nick" placeholder="Type your nick" onEnter={handleEnter} />
open
, x
, y
, items: { id; label; disabled? }[]
on:select={(e) => e.detail /* id */}
<script>
import { ContextMenu } from 'fivem-ui-core';
let menu = { open: true, x: 300, y: 200 };
const items = [{ id: 'copy', label: 'Copy' }, { id: 'del', label: 'Delete', disabled: true }];
</script>
<ContextMenu {items} open={menu.open} x={menu.x} y={menu.y} on:select={(e) => console.log('Selected:', e.detail)} />
Snackbar
component + toasts
store and helpers push/remove/clear
<script>
import { Snackbar, push } from 'fivem-ui-core';
function show() { push('Operation completed', { type: 'success', timeout: 2000 }); }
</script>
<button on:click={show}>Show toast</button>
<Snackbar position="top-right" />
esx
and qb
add prefixes for actions/events (e.g. esx:notify
, qb:ready
).import { esx, qb } from 'fivem-ui-core';
// Listen for Lua event (SendNUIMessage action = 'esx:notify')
const off = esx.on('notify', (data) => {
console.log('ESX notify:', data);
});
// Send response to Lua (RegisterNUICallback 'qb:ready')
await qb.send('ready', { ok: true });
off();
GetParentResourceName()
and POST to https://<resource>/<event>
.sendEvent
does not send network requests — it logs and returns a mock Response { ok: true }
so you can develop UI without errors.jsdom
, Svelte compilation via Vite plugin.Scripts:
npm test # once
npm run test:watch
Scope examples:
PRs/issues welcome.
MIT
Bl4ck3d :)
Przyjazny core UI dla FiveM w Svelte. Dostajesz wspólne API do komunikacji NUI (UI ↔ Lua), prosty system motywów (theme) oraz gotowe komponenty, z których korzysta większość serwerów (Notification, Modal, ProgressBar, StatusBar, Hotbar, RadialMenu).
onEvent
i sendEvent
.npm i fivem-ui-core
fivem-ui-core
.ThemeProvider
(domyślny theme działa od razu, ale możesz podać własny):<script>
import { ThemeProvider, Notification, ProgressBar } from 'fivem-ui-core';
let hp = 75;
const theme = {
colors: {
primary: '#4CC9F0',
success: '#2ecc71',
warning: '#f39c12',
error: '#e74c3c',
surface: '#161616',
onSurface: '#ffffff',
border: '#2a2a2a'
},
radius: '10px',
fontFamily: 'Inter, system-ui, sans-serif'
};
</script>
<ThemeProvider {theme}>
<Notification type="success" message="Zalogowano pomyślnie!" />
<ProgressBar value={hp} max={100} />
</ThemeProvider>
import { onEvent } from 'fivem-ui-core';
onEvent('showNotify', (data) => {
// { type: 'info' | 'success' | 'warning' | 'error', message: string }
console.log('Powiadomienie:', data);
});
import { sendEvent } from 'fivem-ui-core';
await sendEvent('callbackDone', { ok: true });
sendEvent(eventName, data)
– wewnętrznie robi POST do https://<resource>/<eventName>
.import { sendEvent } from 'fivem-ui-core';
await sendEvent('playerReady', { name: 'John' });
Jeśli potrzebujesz większej niezawodności, użyj klienta z wbudowanymi timeoutami, retry (exponential backoff) i loggerem.
import { createNuiClient } from 'fivem-ui-core';
const client = createNuiClient({
timeoutMs: 5000, // domyślnie 5000
retries: 2, // domyślnie 2 próby
backoffMs: 300, // domyślnie 300ms, rośnie wykładniczo per próba
logger: (level, message, meta) => console[level]?.('[nui]', message, meta)
});
// surowy Response
const res = await client.send('inventory:save', { items: [] });
if (!res.ok) throw new Error('Błąd zapisu');
// automatyczny parse JSON
const data = await client.sendJson('player:getProfile', { id: 123 });
Najlepsze praktyki:
sendJson
z walidacją odpowiedzi.Po stronie Lua (client.lua):
-- Rejestrujesz callback o tej samej nazwie
RegisterNUICallback('playerReady', function(data, cb)
print('UI powiedziało:', json.encode(data))
cb({ ok = true })
end)
SendNUIMessage
z action
i data
.action
przez onEvent
.SendNUIMessage({
action = 'showNotify',
data = { type = 'info', message = 'Nowe powiadomienie!' }
})
import { onEvent } from 'fivem-ui-core';
onEvent('showNotify', (data) => {
// zrób np. toast ze stylowaniem wg type
});
SetNuiFocus(true, true)
aby UI mogło przyjmować input.SetNuiFocus(false, false)
.SendNUIMessage.action
↔ onEvent('<action>')
oraz sendEvent('<event>')
↔ RegisterNUICallback('<event>')
.ThemeProvider
.Klucze motywu:
colors.primary|success|warning|error|surface|onSurface|border
radius
– promień zaokrąglenia komponentówfontFamily
– główna czcionkaMinimalny przykład:
<script>
import { ThemeProvider, defaultTheme } from 'fivem-ui-core';
// możesz skopiować defaultTheme i zmienić tylko wybrane pola
const theme = {
...defaultTheme,
colors: {
...defaultTheme.colors,
primary: '#00E5A8'
},
radius: '12px'
};
</script>
<ThemeProvider {theme}>
<slot />
</ThemeProvider>
presetLight
, presetDark
oraz przykładowe serverPalettes
(red/blue/purple).import { presetLight, presetDark, serverPalettes } from 'fivem-ui-core';
// Light
const themeLight = presetLight;
// Dark z podmienioną paletą primary na serwerową "purple"
const themeDarkPurple = {
...presetDark,
colors: {
...presetDark.colors,
primary: serverPalettes.purple.primary
}
};
type = 'info' | 'success' | 'warning' | 'error'
, message
, icon?
<Notification type="success" message="Operacja zakończona" />
open
(bind), title
actions
<script>
let open = true;
</script>
<Modal bind:open title="Przykładowy modal">
Treść modala
<div slot="actions">
<button on:click={() => (open = false)}>Zamknij</button>
</div>
</Modal>
value
, max = 100
, color?
label
(domyślnie procent)<ProgressBar value={75} />
<ProgressBar value={30} color="#ff4d4f" />
value
, max = 100
, size = 48
, stroke = 6
, color?
<ProgressCircle value={hp} max={100} size={64} />
hp
, hunger
, stamina
(0–100)<StatusBar hp={75} hunger={50} stamina={90} />
items: { slot: number; label?: string; icon?: string }[]
, selected?: number
<Hotbar items={[{ slot: 1, label: 'Pistolet' }, { slot: 2, label: 'Apteczka' }]} selected={1} />
open
, items: { id: string; label: string }[]
on:select={(e) => e.detail /* id */}
<RadialMenu
open
items={[{ id: 'anim', label: 'Animacja' }, { id: 'veh', label: 'Pojazd' }]}
on:select={(e) => console.log('Wybrano:', e.detail)}
/>
items: { id; label; description? }[]
, selectedId?
, onSelect?
<script>
import { List } from 'fivem-ui-core';
let selected = null;
</script>
<List items={[{ id: 1, label: 'Opcja A' }, { id: 2, label: 'Opcja B', description: 'Opis' }]} onSelect={(id) => selected = id} />
value
, placeholder?
, label?
, type = 'text'|'password'|'number'
, disabled?
, name?
, id?
, onEnter?
<script>
import { TextInput } from 'fivem-ui-core';
let nick = '';
function handleEnter(v) { console.log('Enter:', v); }
</script>
<TextInput bind:value={nick} label="Nick" placeholder="Wpisz nick" onEnter={handleEnter} />
open
, x
, y
, items: { id; label; disabled? }[]
on:select={(e) => e.detail /* id */}
<script>
import { ContextMenu } from 'fivem-ui-core';
let menu = { open: true, x: 300, y: 200 };
const items = [{ id: 'copy', label: 'Kopiuj' }, { id: 'del', label: 'Usuń', disabled: true }];
</script>
<ContextMenu {items} open={menu.open} x={menu.x} y={menu.y} on:select={(e) => console.log('Wybrano:', e.detail)} />
Snackbar
+ store toasts
oraz helpery push/remove/clear
<script>
import { Snackbar, push } from 'fivem-ui-core';
function show() { push('Operacja zakończona', { type: 'success', timeout: 2000 }); }
</script>
<button on:click={show}>Pokaż toast</button>
<Snackbar position="top-right" />
esx
i qb
, które prefixują zdarzenia/akcje (np. esx:notify
, qb:ready
).import { esx, qb } from 'fivem-ui-core';
// Nasłuch zdarzenia z Lua (SendNUIMessage action = 'esx:notify')
const off = esx.on('notify', (data) => {
console.log('ESX notify:', data);
});
// Wysłanie odpowiedzi do Lua (RegisterNUICallback 'qb:ready')
await qb.send('ready', { ok: true });
off();
GetParentResourceName()
i POST do https://<resource>/<event>
.sendEvent
nie wysyła żądań – loguje dane i zwraca sztuczną odpowiedź Response { ok: true }
, abyś mógł normalnie rozwijać UI bez błędów sieciowych.jsdom
, kompilacja Svelte przez wtyczkę Vite.Skrypty:
npm test # jednorazowo
npm run test:watch
Przykłady zakresu testów:
Jeśli chcesz coś dodać – PR/issue mile widziane.
MIT
Bl4ck3d :)