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|borderradius — 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), titleactions<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|borderradius – 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), titleactions<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 :)