Documentation
v1.0.0 · prefix av-Ringkasan
WAVECORE adalah UI template HTML/CSS/JS statis untuk aplikasi admin: dashboard, data master, form, POS, dan faktur. Tanpa framework, tanpa jQuery, tanpa build step. Cukup satu CSS dan satu JS:
<link href="./assets/style.css" rel="stylesheet"> <script src="./assets/icons/feather.min.js"></script> <script src="./assets/style.js"></script>
Semua class, id, dan variable CSS memakai prefix av- supaya tidak bentrok dengan CSS lain. Ikon memakai Feather (<i data-feather="home"></i>), diganti jadi SVG oleh feather.replace() di style.js.
Struktur Folder
| Path | Isi |
|---|---|
index.html | Landing page portofolio WAVECORE UI. |
home.html | Blank page: layout lengkap (topbar, sidebar, footer) tanpa konten. Titik awal halaman baru. |
documentation.html | Halaman ini. |
login.html | Halaman login (tanpa sidebar). |
pages/*.html | Contoh halaman dan komponen (lihat bagian Halaman). |
assets/style.css | Semua CSS: grid, utilitas, komponen, layout, dark mode, states, POS, invoice, documentation. |
assets/style.js | Sidebar accordion, ripple, toggle sidebar, dark mode, dropdown, tinggi table-freeze, modal. |
assets/icons/feather.min.js | Ikon Feather. |
assets/images/ | Gambar (avatar). |
assets/library/onvshark-* | Library OnvShark: alert, toastify, tags-input, texteditor, select. Masing-masing punya .css, .js, .md. |
data/*.json | Data dummy: datatable.json (tabel aset, pages/tables.html), dataselect-grouping.json (select grouping, pages/form.html). |
doc/*.md | Catatan komponen (table-freeze.md). |
DESIGN.md, PRODUCT.md | Design system dan arah produk. |
Halaman
| Menu | File | Isi |
|---|---|---|
| Pages | ||
| Dashboard | pages/dashboard.html | KPI, grafik SVG, ringkasan. |
| Tables | pages/tables.html | Tabel aset dari data/datatable.json: freeze header, kolom tetap kiri/kanan, sort, pagination. Contoh tabel basic, stripe, small. |
| Modal | pages/modal.html | Semua ukuran modal (default, lg, xl, fullscreen), isi lorem. |
| Alert | pages/alert.html | Komponen .av-alert: default, outline, soft, left border + icon. |
| Badge | pages/badge.html | Default, soft, pill, soft + pill. |
| Button | pages/button.html | Default, outline, soft, rounded, size. |
| Form | pages/form.html | Input, select, checkbox, radio, switch, password, file, color, validasi. |
| Upload | pages/upload.html | Drop zone .av-upload: drag & drop, validasi tipe/ukuran, tile dengan preview gambar. |
| Auth | login.html | Login. |
| OnvShark | ||
| Alert | pages/onvshark-alert.html | Dialog konfirmasi onvsAlert(). |
| Toastify | pages/onvshark-toastify.html | Notifikasi onvsToast(). |
| Tags Input | pages/onvshark-tags.html | OnvTagsInput.init(): bebas, master, creatable. |
| Text Editor | pages/onvshark-texteditor.html | OnvTextEditor.init() + sanitize(). |
| Select | pages/onvshark-select.html | ONVSelect: search server, grouping lokal, multiple checklist. |
| Plugins & Addons | ||
| POS | pages/pos.html | Kasir layar penuh: produk, keranjang, pembayaran (data dummy). |
| Invoice | pages/invoice.html | Form faktur penjualan layar penuh: customer, limit kredit, item, ringkasan (data dummy). |
Membuat Halaman Baru
Salin home.html (di root) atau salah satu file di pages/ (di dalam folder pages/). File di pages/ memakai path ../assets/, link menu tanpa folder, dan Auth ke ../login.html. Menu sidebar ditulis di setiap file, jadi menu baru harus ditambahkan ke semua halaman.
<body data-topbar="dark" data-sidebar="brand" data-sidebar-size="lg">
<div id="av-layout-wrapper">
<header id="av-page-topbar">...</header> <!-- logo, search, dark mode, burger -->
<div class="av-vertical-menu">...</div> <!-- profile + ul#av-side-menu -->
<div class="av-main-content">
<div class="av-page-content">
<div class="av-container-fluid">
<div class="av-row"><div class="av-col-12">
<div class="av-page-title-box av-d-sm-flex av-align-items-center av-justify-content-between">
<h4 class="av-mb-sm-0 av-font-size-18">Judul</h4>
</div>
</div></div>
<!-- konten: av-row > av-col-* > av-card -->
</div>
</div>
<footer class="av-footer">...</footer>
</div>
</div>
</body>
| Atribut body | Nilai |
|---|---|
data-layout-mode | light / dark (diatur tombol bulan/matahari) |
data-topbar | dark |
data-sidebar | brand (biru) / dark |
data-sidebar-size | lg (lebar) / sm (ikon saja) |
Menu: <li class="av-menu-title"> untuk judul grup, <a class="av-has-arrow"> + <ul class="av-sub-menu"> untuk submenu.
Design Token
Sumber: :root di assets/style.css. Pakai variable, jangan hex langsung. Turunan: -soft (latar), -text (teks di atas soft), -border, -rgb (untuk rgba(var(--av-primary-rgb), .25)).
| Variable | Fungsi | Base | -soft | -text | -border | Lainnya | -rgb |
|---|---|---|---|---|---|---|---|
--av-theme | Identitas brand: sidebar, navbar, logo | #1a2647 | #e8f0fb | #012d61 | #3a6fc4 | - | - |
--av-theme-secondary | Pendamping brand | #01397a | #cddcf5 | #012d61 | - | - | - |
--av-primary | Aksi utama: simpan, submit | #1a2647 | #e8f0fb | #012d61 | - | -hover #3a6fc4 | 26, 38, 71 |
--av-secondary | Aksi pendukung: batal, edit | #74788d | #eeeef3 | #4a4e6b | - | -dark #4a4e6b | 116, 120, 141 |
--av-success | Positif: selesai, baik | #2ab57d | #e1f5ee | #0f6e56 | #5dcaa5 | - | 42, 181, 125 |
--av-danger | Kritis: rusak, terlambat, hapus | #fd625e | #feecec | #b93230 | #fda09e | - | 253, 98, 94 |
--av-warning | Perhatian: perlu cek, jatuh tempo | #ffbf53 | #fff5e0 | #9b6a00 | #ffd48a | - | 255, 191, 83 |
--av-info | Netral: terjadwal, notifikasi | #4ba6ef | #e6f1fb | #185fa5 | #85b7eb | - | 75, 166, 239 |
--av-light | Pasif: inactive, disabled, border | #e9e9ef | #f5f5f8 | - | - | - | 233, 233, 239 |
--av-dark | Teks / latar gelap | #343a40 | - | - | - | - | 52, 58, 64 |
--av-accent | Label kategori, tag prioritas (maks 10% layar) | #e83e8c | #fde8f2 | #a31d62 | - | -dark #7a1449 | 232, 62, 140 |
--av-white | Card, modal, input | #fff | - | - | - | - | 255, 255, 255 |
--av-black | Hanya -rgb (shadow, overlay) | - | - | - | - | - | 0, 0, 0 |
--av-body-color | Teks utama | #495057 | - | - | - | - | 73, 80, 87 |
--av-body-bg | Latar halaman | #f8f8f6 | - | - | - | - | 248, 248, 246 |
| Variable | Nilai | Catatan |
|---|---|---|
--av-font-sans-serif | "IBM Plex Sans", sans-serif | Font utama |
--av-font-monospace | SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace | Kode, angka sejajar |
--av-body-font-family | var(--av-font-sans-serif) | |
--av-body-font-size | 0.875rem (14px) | Minimum untuk konten utama |
--av-body-font-weight | 400 | Label 500, heading 600 |
--av-body-line-height | 1.5 | Heading 1.2 |
--av-gradient | linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,0)) | Kilap tombol. Jangan dipakai untuk header dan background |
| Radius | 4px · 6px · 8px | Button/input · modal · card |
| Spacing | 4 · 8 · 16 · 24 · 32px | |
| Shadow | - | Datar saat diam, shadow hanya saat hover/fokus/modal |
Komponen
| Komponen | Class | Contoh |
|---|---|---|
| Button | av-btn + av-btn-{primary|secondary|success|info|warning|danger|light|dark}, av-btn-outline-*, av-btn-soft-*, av-btn-rounded, ukuran av-btn-xsm | av-btn-sm | av-btn-lg |
|
| Badge | av-badge + av-bg-* atau av-badge-soft-*, av-rounded-pill |
Primary Soft Pill |
| Alert | av-alert + av-alert-*, av-alert-outline-*, av-alert-soft-*, av-alert-border-* + av-alert-icon |
Soft info |
| Card | av-card > av-card-header (av-card-title) + av-card-body + av-card-footer. Toolbar tabel: av-card-header-toolbar > av-toolbar-left / av-toolbar-right |
Lihat card di halaman ini |
| Form | av-form-control (+ av-form-control-sm), av-input-group + av-input-group-text, av-col-form-label-sm, av-form-check, av-form-switch. Validasi: is-valid / is-invalid + av-invalid-feedback |
|
| Grid | av-container-fluid > av-row > av-col-{sm|md|lg|xl}-{1..12} |
Breakpoint seperti Bootstrap |
| Utilitas | av-d-flex, av-align-items-center, av-justify-content-between, av-flex-wrap, av-gap-2, av-m*-*, av-p*-*, av-text-muted, av-text-sm-end, av-d-none, av-d-sm-block, av-font-size-* |
Prefix av- dari utilitas Bootstrap |
| Pagination | av-pagination > av-page-item (av-active, av-disabled, av-ellipsis) > av-page-link; varian av-pagination-rounded |
|
Table
Tabel biasa: av-table + av-table-basic (border semua sisi), av-table-stripe (zebra + hover), av-table-sm. Bungkus dengan av-table-responsive untuk scroll horizontal.
Table freeze (header tetap, scroll di dalam):
<div class="av-table-responsive">
<div class="av-table-freeze-wrapper" data-height="70vh"> <!-- default 70vh -->
<div class="av-table-freeze-container">
<table class="av-table-freeze" style="width:100%;">
<thead><tr>
<th class="av-table-freeze-table-freeze-fixed-colomn-header av-th-sort" data-col="0">ID <span class="av-sort-icon">⇅</span></th>
...
<th class="av-table-freeze-fixed-right">Action</th>
</tr></thead>
<tbody><tr>
<td class="av-table-freeze-fixed-colomn">AST-001</td>
...
<td class="av-table-freeze-fixed-right">...</td>
</tr></tbody>
</table>
</div>
</div>
</div>
| Fitur | Cara |
|---|---|
| Tinggi | data-height di .av-table-freeze-wrapper (CSS length apa pun). Dibaca style.js saat halaman dimuat. |
| Kolom tetap kiri | av-table-freeze-table-freeze-fixed-colomn-header (th) + av-table-freeze-fixed-colomn (td) |
| Kolom tetap kanan | av-table-freeze-fixed-right di th dan setiap td |
| Sort | av-th-sort + data-col + av-sort-icon. Logika sort ada di script halaman (pages/tables.html). |
| Pagination | Client-side di pages/tables.html: avRenderTable(data, page), avRenderPagination(total), dropdown #avPerPage. |
| Teks panjang | Sel dipotong … di 200px, tampil penuh saat hover. |
Modal
<button type="button" class="av-btn av-btn-primary" data-av-modal="av-modal_demo">Open</button>
<div class="av-modal" id="av-modal_demo">
<div class="av-modal-dialog av-modal-lg"> <!-- kosong = default | av-modal-lg | av-modal-xl -->
<div class="av-modal-header">
<h5 class="av-modal-title">Title</h5>
<button type="button" class="av-modal-close" data-av-dismiss>✕</button>
</div>
<div class="av-modal-body">...</div>
<div class="av-modal-footer">
<button type="button" class="av-btn av-btn-secondary" data-av-dismiss>Close</button>
</div>
</div>
</div>
- Fullscreen:
<div class="av-modal av-modal-fs-wrap">+<div class="av-modal-dialog av-modal-fullscreen">. data-av-modal="id"membuka,data-av-dismissmenutup. Klik overlay dan Esc juga menutup.- Dari JS:
AvModal.open('id'),AvModal.close().
States
| Class | Fungsi |
|---|---|
av-spinner, av-spinner-sm | Spinner. Tersembunyi, tampil di dalam button[data-loading] atau .av-loading-state. |
av-loading-state | Baris/area "sedang memuat". |
av-empty-state (+ -icon, -title, -text) | Tampilan data kosong atau error. |
av-modal-loading | Overlay loading di dalam modal. |
is-valid, is-invalid | Status validasi di av-form-control. |
av-sr-only | Hanya untuk screen reader. Live region: #av-live-region, #av-live-region-assertive. |
style.js
| Bagian | Fungsi |
|---|---|
| Sidebar accordion | Buka/tutup av-sub-menu; saat sidebar sm, submenu tampil sebagai popup dan menu tunggal sebagai tooltip. |
| Ripple | Efek gelombang saat tombol diklik. |
| Sidebar toggle | #av-vertical-menu-btn (mobile) dan #av-sidebar-profile-toggle: ganti data-sidebar-size lg/sm. |
| Dark mode | #av-mode-setting-btn: ganti data-layout-mode dan data-sidebar. |
| Dropdown user | #av-page-header-user-dropdown + menu di sebelahnya (av-show). |
| Table freeze | Isi tinggi dari data-height. |
| Feather | feather.replace() saat DOMContentLoaded. Panggil lagi setelah render HTML baru yang berisi data-feather. |
| Modal | data-av-modal, data-av-dismiss, overlay, Esc, window.AvModal. |
Library OnvShark
Library mandiri (tanpa dependency) di assets/library/. Include CSS + JS hanya di halaman yang memakainya. Dokumentasi lengkap ada di file .md masing-masing dan di link docs.
| Library | Fungsi | File | Docs |
|---|---|---|---|
| Alert | onvsAlert(icon, type, title, message, buttons) | onvshark-alert/alert.css|js | docs |
| Toastify | onvsToast(icon, type, message, subMessage, close, position) | onvshark-toastify/toastify.css|js | docs |
| Tags Input | OnvTagsInput.init(id, { source, creatable, max, separator }) | onvshark-tags-input/tagsinput.css|js | docs |
| Text Editor | OnvTextEditor.init(id, { tools, color, size }), OnvTextEditor.sanitize(html) | onvshark-texteditor/texteditor.css|js | docs |
| Select | new ONVSelect(select, { url | source, groupField, search, multiple }) | onvshark-select/select.css|js | lihat bagian ONVSelect |
onvsToast(true, 'success', 'Data saved', null, 3000, 'top-right');
onvsAlert(true, 'danger', 'Delete data?', 'This cannot be undone.', {
confirm: { label: 'Delete', icon: 'trash', onClick: () => {} },
cancel : { label: 'Cancel', icon: 'x' }
});
ONVSelect
Dropdown pengganti <select>: cari ke server, gulir per halaman, grup, checklist. Demo: pages/onvshark-select.html. Opsi lengkap: assets/library/onvshark-select/select.md.
1. Pasang CSS & JS
<link href="../assets/library/onvshark-select/select.css" rel="stylesheet"> <script src="../assets/library/onvshark-select/select.js"></script>
2. Buat <select> kosong
<!-- satu pilihan --> <select id="aset" name="aset"></select> <!-- banyak pilihan: tambah multiple + name[] --> <select id="cabang" name="cabang[]" multiple></select>
Opsi tidak perlu ditulis. <select> asli disembunyikan dan tetap menyimpan nilai terpilih, jadi form submit jalan seperti biasa.
3. Jalankan ONVSelect
const aset = new ONVSelect('#aset', {
url: '/api/assets', // data dari server
valueField: 'id_aset', // nilai yang disimpan
formatText: (a) => `${a.id_aset} - ${a.nama_aset}`,
paginationPath: 'meta.pagination',
limit: 20,
fullWidth: true,
onChange: (value, text) => {},
});
aset.getValue(); // 'AST-001'
aset.reset(); // kosongkan
4. Data dari API
ONVSelect memanggil url saat dibuka, saat mengetik (jeda 400 ms), dan saat daftar digulir sampai bawah:
GET /api/assets?page=1&limit=20&search=forklift
Server membalas array di data dan info halaman di paginationPath:
{
"data": [
{ "id_aset": "AST-001", "nama_aset": "Forklift Toyota 3T" }
],
"meta": { "pagination": { "current_page": 1, "last_page": 5 } }
}
Contoh endpoint Laravel (Query Builder):
Route::get('/api/assets', function (Request $request) {
$query = DB::table('assets')->whereNull('deleted_at');
if ($request->search) {
$query->where(fn ($q) => $q
->where('id_aset', 'like', "%{$request->search}%")
->orWhere('nama_aset', 'like', "%{$request->search}%"));
}
$page = $query->orderBy('id_aset')->paginate($request->limit ?? 20);
return [
'data' => $page->items(),
'meta' => ['pagination' => [
'current_page' => $page->currentPage(),
'last_page' => $page->lastPage(),
]],
];
});
Data sudah ada di halaman (tidak perlu server)? Pakai source: [...] sebagai pengganti url, lihat contoh Grouping.
Contoh
1. Search + Scroll Next Page (server)
<select id="avAssetPick" name="avAssetPick"></select>
const avAssetPick = new ONVSelect('#avAssetPick', {
url: 'demo/assets',
valueField: 'id_aset',
formatText: (a) => `${a.id_aset} - ${a.nama_aset} (${a.kategori})`,
paginationPath: 'meta.pagination',
limit: 10,
placeholder: '- Select asset -',
searchPlaceholder: 'Type ID, name, category...',
fullWidth: true,
onChange: (value, text) => console.log(value, text),
});
2. Multiple Checklist (server)
<select id="avCustomerMulti" name="avCustomerMulti[]" multiple></select>
const avCustomerMulti = new ONVSelect('#avCustomerMulti', {
url: 'demo/customers',
valueField: 'id',
formatText: (c) => `${c.code} - ${c.name}`,
paginationPath: 'meta.pagination',
limit: 20,
placeholder: '- Select customers -',
selectedText: '{n} customers selected',
fullWidth: true,
onChange: (values, texts) => console.log(values), // ['c1', 'c3']
});
// mode edit: isi pilihan tersimpan tanpa memanggil onChange
avCustomerMulti.selectOption('c1', 'C1C0000001 - Customer 1', false);
3. Grouping (data lokal)
<select id="avCityPlain" name="avCityPlain"></select>
<select id="avCitySearch" name="avCitySearch"></select>
<select id="avCityMulti" name="avCityMulti[]" multiple></select>
// [{ "id": "bdg", "name": "Bandung", "group": "Jawa Barat" }, ...]
fetch('../data/dataselect-grouping.json')
.then(res => res.json())
.then(json => {
const base = { source: json.data, valueField: 'id', textField: 'name', groupField: 'group', fullWidth: true };
new ONVSelect('#avCityPlain', { ...base, search: false }); // tanpa search
new ONVSelect('#avCitySearch', base); // search
new ONVSelect('#avCityMulti', { ...base, multiple: true }); // search + checklist
});
POS & Invoice
- Halaman layar penuh tanpa sidebar. Body memakai
class="av-pos-page"atauclass="av-inv-page"; aturan body di CSS hanya berlaku untuk class itu. - CSS ada di
style.cssbagian POS (av-pos-*) dan SALES INVOICE (av-inv-*). - Data dummy di objek
DATAdi script halaman. Untuk project nyata, ganti dengan panggilan API (toko/gudang, customer, metode bayar, kategori, produk dengan satuan dan hargaretail/wholesale). - POS: F2 cari, F9 bayar, Esc tutup pembayaran. Enter di kolom cari dengan barcode/SKU persis langsung masuk keranjang. Stok dihitung dalam satuan dasar.
- Invoice: cek limit kredit (piutang + sisa tagihan), uang muka, termin, Save as Draft, Post (mengunci form). Print memanggil
window.print(). - Mode gelap disimpan di
localStoragekeyav-mode.
Aturan
- Semua class dan id baru memakai prefix
av-(class halaman khusus:av-pos-,av-inv-,av-doc-). Library OnvShark memakai prefix sendiri (onvs-). - CSS ditulis di
assets/style.css, bukan<style>di halaman. Kalau tetap perlu inline untuk satu halaman, beri komentar alasannya. - Warna lewat variable
--av-*. Tanpa gradient untuk header dan background. - Teks yang ditampilkan dari data selalu di-escape (
avEscapeHtml/esc). HTML dari editor ditampilkan lewatOnvTextEditor.sanitize(). - Setiap elemen interaktif punya label (
aria-label,title, atau teks) dan bisa dipakai dengan keyboard. - Menu sidebar ditulis manual di setiap halaman; menambah menu = ubah semua file.