@skyworx/dynamic-ui 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2740 -2
- package/dist/DrilldownUtil-esP8Tbvd.js +707 -0
- package/dist/NavigatedViewer-DCtAS2Mp.js +10470 -0
- package/dist/dynamic-ui.css +1 -9
- package/dist/dynamic-ui.es.js +90 -36662
- package/dist/dynamic-ui.umd.js +324 -55
- package/dist/index-B3YnmBLP.js +107670 -0
- package/dist/index.d.ts +1061 -614
- package/dist/viewer-BWRZF9JG.js +3806 -0
- package/package.json +130 -121
- package/src/index.d.ts +1061 -614
package/README.md
CHANGED
|
@@ -1,2 +1,2740 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# @gemafajarramadhan/dynamic-ui
|
|
2
|
+
|
|
3
|
+
Library **Vue 3** component yang bisa digunakan di berbagai framework (Vue, React, Angular, Svelte, dll.) untuk membuat **Dynamic Form** dan **DataTable** otomatis dari JSON schema, sekaligus menyediakan komponen UI yang kaya fitur.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Daftar Isi
|
|
8
|
+
|
|
9
|
+
- [Fitur](#fitur)
|
|
10
|
+
- [Instalasi](#instalasi)
|
|
11
|
+
- [⚡ Konfigurasi API (WAJIB)](#-konfigurasi-api-wajib)
|
|
12
|
+
- [Vue 3](#konfigurasi-api-di-vue-3)
|
|
13
|
+
- [Nuxt 3](#konfigurasi-api-di-nuxt-3)
|
|
14
|
+
- [React / Next.js](#konfigurasi-api-di-react--nextjs)
|
|
15
|
+
- [Angular](#konfigurasi-api-di-angular)
|
|
16
|
+
- [Vanilla JS (window global)](#konfigurasi-api-via-window-global)
|
|
17
|
+
- [Komponen Base (Vue Components)](#komponen-base-vue-components)
|
|
18
|
+
- [Daftar Komponen](#daftar-komponen)
|
|
19
|
+
- [Penggunaan di Vue 3 (Import Per Komponen)](#penggunaan-di-vue-3-import-per-komponen)
|
|
20
|
+
- [Penggunaan di Vue 3 (Global Plugin)](#penggunaan-di-vue-3-global-plugin)
|
|
21
|
+
- [Penggunaan di Nuxt 3](#penggunaan-di-nuxt-3-vue-components)
|
|
22
|
+
- [DCodeDataTable – Tabel Dinamis](#dcodedatatable--tabel-dinamis)
|
|
23
|
+
- [Props Utama](#props-utama)
|
|
24
|
+
- [Pola 1 — fetchHandler (MFE)](#pola-1--fetchhandler--disarankan-untuk-mfe)
|
|
25
|
+
- [Pola 2 — Static Data](#pola-2--static-data-consumer-fetch-di-luar)
|
|
26
|
+
- [Pola 3 — Realtime Socket](#pola-3--realtime-socket-consumer-kelola-socket)
|
|
27
|
+
- [Format Response](#format-response-yang-didukung)
|
|
28
|
+
- [Expose Methods](#expose-methods-via-ref)
|
|
29
|
+
- [DCodeAutoComplete – Komponen dengan API](#dcodeautocomplete--komponen-dengan-api)
|
|
30
|
+
- [Web Components (Framework Agnostic)](#web-components-framework-agnostic)
|
|
31
|
+
- [API Referensi Web Components](#api-referensi-web-components)
|
|
32
|
+
- [Logic Registry API](#logic-registry-api)
|
|
33
|
+
- [Penggunaan di Berbagai Framework](#penggunaan-di-berbagai-framework)
|
|
34
|
+
- [Vanilla HTML / JavaScript](#1-vanilla-html--javascript)
|
|
35
|
+
- [Vue 3 (Web Components)](#2-vue-3-web-components)
|
|
36
|
+
- [React](#3-react)
|
|
37
|
+
- [Next.js (App Router)](#4-nextjs-app-router)
|
|
38
|
+
- [Angular](#5-angular)
|
|
39
|
+
- [Nuxt 3 (Web Components)](#6-nuxt-3-web-components)
|
|
40
|
+
- [Custom Logic Registry](#custom-logic-registry)
|
|
41
|
+
- [TypeScript Support](#typescript-support)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Fitur
|
|
46
|
+
|
|
47
|
+
- 🧩 **Web Components** – Dapat digunakan di framework **apapun** (React, Vue, Angular, Svelte, dll.) atau di HTML biasa.
|
|
48
|
+
- 📋 **AutoLayoutForm** – Generate form otomatis dari JSON schema dengan validasi dan konfigurasi fleksibel.
|
|
49
|
+
- 📊 **AutoLayoutDatatable** – Generate tabel dengan pagination, sorting, filtering, dan action button dari JSON.
|
|
50
|
+
- 🔌 **Logic Registry** – Daftarkan custom business logic dari aplikasi host tanpa perlu memodifikasi library.
|
|
51
|
+
- 🎨 **Style Bundled** – CSS sudah ter-bundle, cukup import satu file style.
|
|
52
|
+
- 🌐 **i18n Ready** – Mendukung internasionalisasi (vue-i18n).
|
|
53
|
+
- 🔐 **API Config** – Base URL dan auth token diambil dari **host project**, bukan dari env package ini.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Instalasi
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# npm
|
|
61
|
+
npm install @gemafajarramadhan/dynamic-ui
|
|
62
|
+
|
|
63
|
+
# yarn
|
|
64
|
+
yarn add @gemafajarramadhan/dynamic-ui
|
|
65
|
+
|
|
66
|
+
# pnpm
|
|
67
|
+
pnpm add @gemafajarramadhan/dynamic-ui
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ⚡ Konfigurasi API (WAJIB)
|
|
73
|
+
|
|
74
|
+
> **Penting:** Komponen seperti `DCodeAutoComplete` melakukan HTTP request ke API. Base URL dan auth token **diambil dari host project**, bukan dari `.env` package ini. Anda **wajib** mengkonfigurasi ini di aplikasi host sebelum menggunakan komponen.
|
|
75
|
+
|
|
76
|
+
Library menggunakan sistem konfigurasi terpusat via `setApiConfig()` (atau via Vue Plugin). Berikut urutan prioritas resolusi base URL:
|
|
77
|
+
|
|
78
|
+
| Prioritas | Sumber |
|
|
79
|
+
|-----------|--------|
|
|
80
|
+
| 1️⃣ Tertinggi | `apiBaseUrl` via plugin / `setApiConfig()` |
|
|
81
|
+
| 2️⃣ | `window.__MICRO_API_BASE_URL__` (global) |
|
|
82
|
+
| 3️⃣ | `import.meta.env.VITE_API_BASE_URL` (env host) |
|
|
83
|
+
| 4️⃣ Terendah | `''` (same-origin fallback) |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Konfigurasi API di Vue 3
|
|
88
|
+
|
|
89
|
+
Cara paling direkomendasikan: lewatkan config saat `app.use()`, atau gunakan `setApiConfig` secara langsung. Hal ini **sangat penting** agar request API dari komponen (seperti `DCodeAutoComplete`) mem-bypass native `fetch` bawaan dan menggunakan instance HTTP (seperti Axios) milik project host Anda. Dengan begitu, request otomatis melewati interceptor (untuk inject token) dan mengarah ke base URL yang tepat saat di production.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
// main.js
|
|
93
|
+
import { createApp } from 'vue'
|
|
94
|
+
import App from './App.vue'
|
|
95
|
+
import DynamicUI, { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
96
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
97
|
+
|
|
98
|
+
// Contoh import instance Axios / useApi dari host project Anda
|
|
99
|
+
import axiosInstance from './plugins/axios'
|
|
100
|
+
// import { useApi } from './composables/useApi'
|
|
101
|
+
|
|
102
|
+
const app = createApp(App)
|
|
103
|
+
|
|
104
|
+
// OPSI 1: Konfigurasi memanggil fungsi (Bisa dipanggil di luar instalasi plugin)
|
|
105
|
+
setApiConfig({
|
|
106
|
+
// WAJIB: Bypass fetch bawaan, gunakan axios dari project host
|
|
107
|
+
apiClient: async (url, method, data) => {
|
|
108
|
+
return axiosInstance({ url, method, data })
|
|
109
|
+
/* ATAU MENGGUNAKAN useApi bawaan project host
|
|
110
|
+
const { apiUrl } = useApi()
|
|
111
|
+
return apiUrl(url, method || 'GET', data, { disableGlobalLoading: true })
|
|
112
|
+
*/
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Base URL API dari env HOST project
|
|
116
|
+
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
|
|
117
|
+
|
|
118
|
+
// (Opsional jika apiClient sudah memiliki interceptor token)
|
|
119
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// OPSI 2: Lewatkan pada saat app.use (Berlaku jika tidak menggunakan setApiConfig di atas)
|
|
123
|
+
app.use(DynamicUI, {
|
|
124
|
+
apiClient: async (url, method, data) => axiosInstance({ url, method, data }),
|
|
125
|
+
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
app.mount('#app')
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
> File `.env` di host project Anda:
|
|
132
|
+
> ```env
|
|
133
|
+
> VITE_API_BASE_URL=https://api.example.com
|
|
134
|
+
> ```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### Konfigurasi API di Nuxt 3
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// plugins/dynamic-ui.ts
|
|
142
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
143
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
144
|
+
|
|
145
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
146
|
+
const config = useRuntimeConfig()
|
|
147
|
+
|
|
148
|
+
nuxtApp.vueApp.use(DynamicUI, {
|
|
149
|
+
// Ambil dari runtimeConfig Nuxt
|
|
150
|
+
apiBaseUrl: config.public.apiBaseUrl,
|
|
151
|
+
|
|
152
|
+
getAuthToken: () => useCookie('access_token').value ?? null,
|
|
153
|
+
|
|
154
|
+
onUnauthorized: () => {
|
|
155
|
+
navigateTo('/login')
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> File `nuxt.config.ts` di host project:
|
|
162
|
+
> ```ts
|
|
163
|
+
> export default defineNuxtConfig({
|
|
164
|
+
> runtimeConfig: {
|
|
165
|
+
> public: {
|
|
166
|
+
> apiBaseUrl: process.env.VITE_API_BASE_URL, // set di .env
|
|
167
|
+
> },
|
|
168
|
+
> },
|
|
169
|
+
> })
|
|
170
|
+
> ```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Konfigurasi API di React / Next.js
|
|
175
|
+
|
|
176
|
+
Gunakan fungsi `setApiConfig()` secara langsung (tidak perlu Vue plugin).
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// src/lib/dynamic-ui-config.ts (jalankan sebelum render app)
|
|
180
|
+
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
181
|
+
|
|
182
|
+
setApiConfig({
|
|
183
|
+
apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
|
|
184
|
+
|
|
185
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
186
|
+
|
|
187
|
+
onUnauthorized: () => {
|
|
188
|
+
window.location.href = '/login'
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Panggil di entry point:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
// app/layout.tsx atau _app.tsx
|
|
197
|
+
'use client'
|
|
198
|
+
import '@/lib/dynamic-ui-config' // ← jalankan konfigurasi lebih dulu
|
|
199
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> File `.env.local` di host project Next.js:
|
|
203
|
+
> ```env
|
|
204
|
+
> NEXT_PUBLIC_API_BASE_URL=https://api.example.com
|
|
205
|
+
> ```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### Konfigurasi API di Angular
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// main.ts
|
|
213
|
+
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
214
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
215
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
216
|
+
|
|
217
|
+
// Konfigurasi sebelum bootstrap
|
|
218
|
+
setApiConfig({
|
|
219
|
+
apiBaseUrl: (window as any).__env?.API_BASE_URL || 'https://api.example.com',
|
|
220
|
+
|
|
221
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
222
|
+
|
|
223
|
+
onUnauthorized: () => {
|
|
224
|
+
window.location.href = '/login'
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
platformBrowserDynamic().bootstrapModule(AppModule)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### Konfigurasi API via Window Global
|
|
234
|
+
|
|
235
|
+
Alternatif tanpa import fungsi – set sebelum library dimuat:
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<script>
|
|
239
|
+
// Set sebelum import library
|
|
240
|
+
window.__MICRO_API_BASE_URL__ = 'https://api.example.com'
|
|
241
|
+
window.__MICRO_AUTH_TOKEN__ = 'your-auth-token' // atau ambil dari cookie/localStorage
|
|
242
|
+
</script>
|
|
243
|
+
<script type="module" src="./main.js"></script>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Komponen Base (Vue Components)
|
|
249
|
+
|
|
250
|
+
Selain Web Components, library ini juga menyediakan **27 komponen Vue** yang bisa digunakan langsung di aplikasi Vue 3, Nuxt 3, dan framework apapun yang mendukung Vue.
|
|
251
|
+
|
|
252
|
+
### Daftar Komponen
|
|
253
|
+
|
|
254
|
+
| Komponen | Deskripsi |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `DCodeTextField` | Input text dengan validasi, text case, filter karakter |
|
|
257
|
+
| `DCodeTextarea` | Textarea dengan auto-resize dan validasi |
|
|
258
|
+
| `DCodeButton` | Tombol dengan variant, warna kustom, tooltip, dan animasi |
|
|
259
|
+
| `DCodeCard` | Kartu container dengan styling bawaan |
|
|
260
|
+
| `DCodeCheckbox` | Checkbox dengan label |
|
|
261
|
+
| `DCodeSwitch` | Toggle switch |
|
|
262
|
+
| `DCodeRadioCustom` | Radio button dengan opsi kustom |
|
|
263
|
+
| `DCodeLabel` | Label untuk form |
|
|
264
|
+
| `DCodeAutoComplete` | Input autocomplete dengan pencarian dan fetch API otomatis |
|
|
265
|
+
| `DCodeMultiSelect` | Dropdown multi-pilihan |
|
|
266
|
+
| `DCodeDatePicker` | Date picker kalender |
|
|
267
|
+
| `DCodeCurrencyField` | Input nilai mata uang |
|
|
268
|
+
| `DCodeOtpInput` | Input OTP digit-per-digit |
|
|
269
|
+
| `DCodeFileField` | Input file dengan preview |
|
|
270
|
+
| `DCodeImageField` | Input gambar dengan preview |
|
|
271
|
+
| `DCodeDropzone` | Drag-and-drop upload area |
|
|
272
|
+
| `DCodeUploadFile` | Upload file terintegrasi |
|
|
273
|
+
| `DCodeFileResult` | Tampilan hasil file yang diupload |
|
|
274
|
+
| `DCodeImageResult` | Tampilan hasil gambar yang diupload |
|
|
275
|
+
| `DCodeImageCropperDialog` | Dialog crop gambar |
|
|
276
|
+
| `DCodeDialog` | Modal dialog |
|
|
277
|
+
| `DCodeDialogCloseBtn` | Tombol tutup untuk dialog |
|
|
278
|
+
| `DCodeIconDropdown` | Dropdown ikon |
|
|
279
|
+
| `DCodeProgressBar` | Progress bar loading |
|
|
280
|
+
| `DCodeTimelineWithIcons` | Timeline dengan ikon |
|
|
281
|
+
| `DCodeWizard` | Wizard / stepper multi-langkah |
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### Penggunaan di Vue 3 (Import Per Komponen)
|
|
286
|
+
|
|
287
|
+
Cara paling ringan: import hanya komponen yang diperlukan.
|
|
288
|
+
|
|
289
|
+
```vue
|
|
290
|
+
<!-- MyForm.vue -->
|
|
291
|
+
<script setup>
|
|
292
|
+
import { DCodeTextField, DCodeButton } from '@gemafajarramadhan/dynamic-ui'
|
|
293
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
294
|
+
</script>
|
|
295
|
+
|
|
296
|
+
<template>
|
|
297
|
+
<form>
|
|
298
|
+
<DCodeTextField
|
|
299
|
+
v-model="form.name"
|
|
300
|
+
label="Nama Lengkap"
|
|
301
|
+
placeholder="Masukkan nama..."
|
|
302
|
+
:clearable="true"
|
|
303
|
+
/>
|
|
304
|
+
|
|
305
|
+
<DCodeTextField
|
|
306
|
+
v-model="form.email"
|
|
307
|
+
label="Email"
|
|
308
|
+
valueType="email"
|
|
309
|
+
placeholder="nama@email.com"
|
|
310
|
+
/>
|
|
311
|
+
|
|
312
|
+
<DCodeButton
|
|
313
|
+
text="Simpan"
|
|
314
|
+
variant="default"
|
|
315
|
+
bgColor="primary"
|
|
316
|
+
type="submit"
|
|
317
|
+
/>
|
|
318
|
+
</form>
|
|
319
|
+
</template>
|
|
320
|
+
|
|
321
|
+
<script setup>
|
|
322
|
+
import { reactive } from 'vue'
|
|
323
|
+
|
|
324
|
+
const form = reactive({ name: '', email: '' })
|
|
325
|
+
</script>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### Penggunaan di Vue 3 (Global Plugin)
|
|
331
|
+
|
|
332
|
+
Install semua komponen sekaligus agar bisa digunakan di mana saja tanpa import per file.
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
// main.js
|
|
336
|
+
import { createApp } from 'vue'
|
|
337
|
+
import App from './App.vue'
|
|
338
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
339
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
340
|
+
|
|
341
|
+
const app = createApp(App)
|
|
342
|
+
|
|
343
|
+
app.use(DynamicUI, {
|
|
344
|
+
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
|
|
345
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
app.mount('#app')
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Setelah itu, semua komponen bisa digunakan **tanpa import** di setiap file:
|
|
352
|
+
|
|
353
|
+
```vue
|
|
354
|
+
<!-- Tidak perlu import lagi! -->
|
|
355
|
+
<template>
|
|
356
|
+
<DCodeTextField v-model="name" label="Nama" />
|
|
357
|
+
<DCodeButton text="Submit" />
|
|
358
|
+
<DCodeDatePicker v-model="date" label="Tanggal" />
|
|
359
|
+
</template>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### Penggunaan di Nuxt 3 (Vue Components)
|
|
365
|
+
|
|
366
|
+
Buat plugin Nuxt untuk mendaftarkan semua komponen secara global:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
// plugins/dynamic-ui.ts
|
|
370
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
371
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
372
|
+
|
|
373
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
374
|
+
const config = useRuntimeConfig()
|
|
375
|
+
|
|
376
|
+
nuxtApp.vueApp.use(DynamicUI, {
|
|
377
|
+
apiBaseUrl: config.public.apiBaseUrl,
|
|
378
|
+
getAuthToken: () => useCookie('access_token').value ?? null,
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Kemudian gunakan di page atau component manapun:
|
|
384
|
+
|
|
385
|
+
```vue
|
|
386
|
+
<!-- pages/index.vue -->
|
|
387
|
+
<template>
|
|
388
|
+
<div>
|
|
389
|
+
<DCodeTextField v-model="search" label="Pencarian" :clearable="true" />
|
|
390
|
+
<DCodeButton text="Cari" variant="default" />
|
|
391
|
+
</div>
|
|
392
|
+
</template>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## DCodeDataTable – Tabel Dinamis
|
|
398
|
+
|
|
399
|
+
`DCodeDataTable` adalah komponen tabel dinamis yang mendukung **pagination server-side**, **sorting**, **filtering**, **action buttons**, dan **badge kondisional** — seluruhnya dikonfigurasi via JSON schema.
|
|
400
|
+
|
|
401
|
+
Komponen ini dirancang sebagai **micro-frontend**: consumer bertanggung jawab penuh atas HTTP client, auth token, dan sistem permission.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### Props Utama
|
|
406
|
+
|
|
407
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
408
|
+
|------|------|---------|-----------|
|
|
409
|
+
| `table-config` | `Object` | — | Config JSON (kolom, filter, action, pagination) |
|
|
410
|
+
| `fetch-handler` | `(params) => Promise<any>` | — | **[MFE]** Consumer kontrol penuh fetch. Component tidak hit endpoint sama sekali. |
|
|
411
|
+
| `http-client` | `({ url, method, data }) => Promise<any>` | — | **[MFE]** Axios instance consumer untuk action tombol row/header (DELETE, PATCH, dll.) |
|
|
412
|
+
| `action-access-checker` | `(path, action) => boolean` | `() => true` | **[MFE]** Fungsi RBAC dari consumer. Default: semua action allow. |
|
|
413
|
+
| `ignore-permissions` | `boolean` | `false` | Matikan filter permission sepenuhnya |
|
|
414
|
+
| `data` | `any[]` | `[]` | Data statis (tanpa API); pakai jika consumer sudah fetch di luar |
|
|
415
|
+
| `loading` | `boolean` | `false` | Loading state untuk mode data statis |
|
|
416
|
+
| `columns` | `TableColumn[]` | `[]` | Kolom manual (tanpa table-config) |
|
|
417
|
+
| `per-page` | `number` | `10` | Jumlah baris per halaman awal |
|
|
418
|
+
| `show-pagination` | `boolean` | `true` | Tampilkan pagination |
|
|
419
|
+
| `has-actions` | `boolean` | `false` | Tampilkan kolom aksi (untuk mode kolom manual) |
|
|
420
|
+
| `api-url` | `string` | — | URL endpoint langsung (tanpa table-config) |
|
|
421
|
+
| `api-method` | `string` | `'POST'` | HTTP method untuk apiUrl |
|
|
422
|
+
| `api-params` | `object` | `{}` | Parameter tetap yang selalu disertakan di setiap request |
|
|
423
|
+
|
|
424
|
+
### Events
|
|
425
|
+
|
|
426
|
+
| Event | Payload | Deskripsi |
|
|
427
|
+
|-------|---------|-----------|
|
|
428
|
+
| `@action` | `{ action, row, selection? }` | Dipanggil saat tombol action diklik |
|
|
429
|
+
| `@update:selection` | `any[]` | Dipanggil saat checkbox selection berubah |
|
|
430
|
+
| `@update:page` | `number` | Dipanggil saat halaman berganti |
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
### Pola 1 — `fetchHandler` ⭐ Disarankan untuk MFE
|
|
435
|
+
|
|
436
|
+
Consumer kontrol penuh HTTP. Component tidak hit endpoint sama sekali — cocok untuk menghindari error 401.
|
|
437
|
+
|
|
438
|
+
```vue
|
|
439
|
+
<template>
|
|
440
|
+
<DCodeDataTable
|
|
441
|
+
:table-config="tableConfig"
|
|
442
|
+
:fetch-handler="fetchUsers"
|
|
443
|
+
:http-client="httpClient"
|
|
444
|
+
:action-access-checker="checkPermission"
|
|
445
|
+
@action="handleAction"
|
|
446
|
+
/>
|
|
447
|
+
</template>
|
|
448
|
+
|
|
449
|
+
<script setup lang="ts">
|
|
450
|
+
import { DCodeDataTable } from '@gemafajarramadhan/dynamic-ui'
|
|
451
|
+
import axios from '@/lib/axios' // axios instance consumer — interceptor token sudah terpasang
|
|
452
|
+
|
|
453
|
+
// 1. fetchHandler: dipanggil tiap page/filter/sort/search change
|
|
454
|
+
// params: { page, perPage, keyword?, ...filters }
|
|
455
|
+
const fetchUsers = async (params: Record<string, any>) => {
|
|
456
|
+
const { data } = await axios.post('/api/users/list', params)
|
|
457
|
+
return data
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 2. httpClient: untuk call dari tombol action row (delete, toggle, dll.)
|
|
461
|
+
const httpClient = async ({ url, method, data }: any) => {
|
|
462
|
+
const res = await axios({ url, method, data })
|
|
463
|
+
return res.data
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 3. Permission checker dari sistem RBAC consumer
|
|
467
|
+
const checkPermission = (path: string, action: string): boolean => {
|
|
468
|
+
return useAuthStore().can(`${path}:${action}`)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 4. Handle action event dari tabel
|
|
472
|
+
const handleAction = ({ action, row, selection }: any) => {
|
|
473
|
+
if (action === 'edit') router.push(`/users/${row.id}/edit`)
|
|
474
|
+
if (action === 'delete') deleteUser(row.id)
|
|
475
|
+
if (action === 'export') exportSelected(selection)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// 5. Konfigurasi tabel via JSON schema
|
|
479
|
+
const tableConfig = {
|
|
480
|
+
headers: [
|
|
481
|
+
{ key: 'no', labelID: 'No', labelEN: 'No', align: 'CENTER' },
|
|
482
|
+
{ key: 'name', labelID: 'Nama', labelEN: 'Name', align: 'LEFT' },
|
|
483
|
+
{ key: 'email', labelID: 'Email', labelEN: 'Email', align: 'LEFT' },
|
|
484
|
+
{
|
|
485
|
+
key: 'status',
|
|
486
|
+
labelID: 'Status', labelEN: 'Status',
|
|
487
|
+
custom: {
|
|
488
|
+
type: 'BADGE',
|
|
489
|
+
conditionBadge: [
|
|
490
|
+
{ value: 'active', labelID: 'Aktif', labelEN: 'Active', color: 'success' },
|
|
491
|
+
{ value: 'inactive', labelID: 'Nonaktif', labelEN: 'Inactive', color: 'danger' },
|
|
492
|
+
],
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
key: 'action',
|
|
497
|
+
labelID: 'Aksi', labelEN: 'Actions',
|
|
498
|
+
custom: {
|
|
499
|
+
type: 'ACTION',
|
|
500
|
+
actionCustom: [
|
|
501
|
+
{
|
|
502
|
+
permission: 'EDIT',
|
|
503
|
+
propsActionCustom: {
|
|
504
|
+
labelID: 'Edit', labelEN: 'Edit',
|
|
505
|
+
icon: 'SquarePen', actionType: 'edit', bgColor: 'primary',
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
permission: 'DELETE',
|
|
510
|
+
requireConfirm: true,
|
|
511
|
+
propCustomMessage: {
|
|
512
|
+
titleID: 'Hapus Data', titleEN: 'Delete',
|
|
513
|
+
messageID: 'Yakin ingin menghapus?', messageEN: 'Are you sure?',
|
|
514
|
+
},
|
|
515
|
+
propsActionCustom: {
|
|
516
|
+
labelID: 'Hapus', labelEN: 'Delete',
|
|
517
|
+
icon: 'Trash2', actionType: 'delete', bgColor: 'red',
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
filters: [
|
|
525
|
+
{
|
|
526
|
+
seq: 1, model: 'name', labelID: 'Nama', labelEN: 'Name', type: 'TEXT',
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
seq: 2, model: 'status', labelID: 'Status', labelEN: 'Status', type: 'SELECT',
|
|
530
|
+
propsFilters: {
|
|
531
|
+
items: [
|
|
532
|
+
{ value: 'active', label: 'Active' },
|
|
533
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
actionGlobal: [
|
|
539
|
+
{
|
|
540
|
+
permission: 'CREATE',
|
|
541
|
+
propsActionGlobal: {
|
|
542
|
+
labelID: 'Tambah', labelEN: 'Add New',
|
|
543
|
+
icon: 'Plus', variant: 'default',
|
|
544
|
+
route: '/users/create',
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
pagination: true,
|
|
549
|
+
}
|
|
550
|
+
</script>
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
### Pola 2 — Static Data (consumer fetch di luar)
|
|
556
|
+
|
|
557
|
+
Pakai ketika consumer sudah fetch data sendiri dan ingin sekadar menampilkan tabel.
|
|
558
|
+
|
|
559
|
+
```vue
|
|
560
|
+
<template>
|
|
561
|
+
<DCodeDataTable
|
|
562
|
+
:columns="columns"
|
|
563
|
+
:data="users"
|
|
564
|
+
:loading="isLoading"
|
|
565
|
+
has-actions
|
|
566
|
+
ignore-permissions
|
|
567
|
+
@action="handleAction"
|
|
568
|
+
/>
|
|
569
|
+
</template>
|
|
570
|
+
|
|
571
|
+
<script setup lang="ts">
|
|
572
|
+
import { ref, onMounted } from 'vue'
|
|
573
|
+
import axios from '@/lib/axios'
|
|
574
|
+
|
|
575
|
+
const users = ref([])
|
|
576
|
+
const isLoading = ref(false)
|
|
577
|
+
|
|
578
|
+
onMounted(async () => {
|
|
579
|
+
isLoading.value = true
|
|
580
|
+
const { data } = await axios.get('/api/users')
|
|
581
|
+
users.value = data.data
|
|
582
|
+
isLoading.value = false
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
const columns = [
|
|
586
|
+
{ key: 'no', label: 'No', align: 'center' },
|
|
587
|
+
{ key: 'name', label: 'Nama', align: 'left' },
|
|
588
|
+
{ key: 'email', label: 'Email', align: 'left' },
|
|
589
|
+
]
|
|
590
|
+
</script>
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### Pola 3 — Realtime Socket (consumer kelola socket)
|
|
596
|
+
|
|
597
|
+
Consumer connect ke WebSocket sendiri, push data ke tabel melalui `:data`.
|
|
598
|
+
|
|
599
|
+
```vue
|
|
600
|
+
<template>
|
|
601
|
+
<DCodeDataTable :columns="columns" :data="realtimeData" ignore-permissions />
|
|
602
|
+
</template>
|
|
603
|
+
|
|
604
|
+
<script setup lang="ts">
|
|
605
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
606
|
+
import { io } from 'socket.io-client'
|
|
607
|
+
|
|
608
|
+
const realtimeData = ref([])
|
|
609
|
+
let socket: any = null
|
|
610
|
+
|
|
611
|
+
onMounted(() => {
|
|
612
|
+
socket = io('wss://your-server.com', {
|
|
613
|
+
auth: { token: localStorage.getItem('token') }
|
|
614
|
+
})
|
|
615
|
+
socket.emit('dashboard:list', { page: 1, perPage: 10 })
|
|
616
|
+
socket.on('dashboard:result', (res: any) => {
|
|
617
|
+
realtimeData.value = res.data ?? res
|
|
618
|
+
})
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
onUnmounted(() => socket?.disconnect())
|
|
622
|
+
</script>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
### Format Response yang Didukung
|
|
628
|
+
|
|
629
|
+
Component secara otomatis mendeteksi berbagai format response API:
|
|
630
|
+
|
|
631
|
+
```json
|
|
632
|
+
{ "data": [], "totalData": 100 } ✅ (disarankan)
|
|
633
|
+
{ "data": [], "maxPage": 10 } ✅
|
|
634
|
+
{ "items": [], "total": 100 } ✅
|
|
635
|
+
{ "list": [], "total": 100 } ✅
|
|
636
|
+
{ "rows": [], "count": 100 } ✅
|
|
637
|
+
{ "result": [], "total": 100 } ✅
|
|
638
|
+
[] ✅ (array langsung)
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
### Expose Methods (via `ref`)
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
const tableRef = ref()
|
|
647
|
+
|
|
648
|
+
tableRef.value.fetchData() // Refresh data (panggil ulang fetchHandler)
|
|
649
|
+
tableRef.value.handleRefresh() // Reset filter & refresh
|
|
650
|
+
tableRef.value.resetFilters() // Reset nilai filter saja (tanpa fetch)
|
|
651
|
+
// Reactive nilai filter saat ini
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
```vue
|
|
655
|
+
<DCodeDataTable ref="tableRef" :fetch-handler="fetchUsers" ... />
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## DCodeAutoComplete – Komponen dengan API
|
|
661
|
+
|
|
662
|
+
`DCodeAutoComplete` adalah komponen autocomplete yang bisa **langsung fetch data dari API** menggunakan konfigurasi yang sudah di-set di host project.
|
|
663
|
+
|
|
664
|
+
> ⚠️ Pastikan sudah mengatur [Konfigurasi API](#-konfigurasi-api-wajib) sebelum menggunakan fitur endpoint.
|
|
665
|
+
|
|
666
|
+
### Perilaku Default
|
|
667
|
+
|
|
668
|
+
- Setiap fetch otomatis mengirimkan `page=1` dan `perPage=100` sebagai query parameter default.
|
|
669
|
+
- Nilai ini bisa di-override via prop `apiParams`.
|
|
670
|
+
|
|
671
|
+
### Props Utama
|
|
672
|
+
|
|
673
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
674
|
+
|------|------|---------|-----------|
|
|
675
|
+
| `modelValue` | `any` | — | Value terpilih (v-model) |
|
|
676
|
+
| `endpoint.apiUrl` | `string` | — | URL endpoint API (path atau full URL) |
|
|
677
|
+
| `endpoint.apiMethod` | `string` | `'GET'` | HTTP method |
|
|
678
|
+
| `apiParams` | `object` | `{}` | Query params tambahan (di-merge & bisa override default) |
|
|
679
|
+
| `itemTitle` | `string\|Function` | `'name'` | Field nama tampilan dari tiap item |
|
|
680
|
+
| `itemValue` | `string\|Function` | `'id'` | Field nilai dari tiap item |
|
|
681
|
+
| `returnObject` | `boolean` | `false` | Jika `true`, v-model menyimpan objek penuh, bukan hanya `id` |
|
|
682
|
+
| `clearable` | `boolean` | `false` | Tampilkan tombol hapus pilihan |
|
|
683
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan komponen |
|
|
684
|
+
| `autoFetchOnOpen` | `boolean` | `true` | Fetch otomatis saat dropdown dibuka pertama kali |
|
|
685
|
+
| `autoFetchOnMount` | `boolean` | `false` | Fetch otomatis saat komponen pertama kali di-mount |
|
|
686
|
+
|
|
687
|
+
### Contoh Penggunaan
|
|
688
|
+
|
|
689
|
+
#### Fetch dari API dengan URL langsung
|
|
690
|
+
|
|
691
|
+
```vue
|
|
692
|
+
<template>
|
|
693
|
+
<DCodeAutoComplete
|
|
694
|
+
v-model="selectedId"
|
|
695
|
+
label="Pilih Departemen"
|
|
696
|
+
:endpoint="{ apiUrl: '/api/master/departemen', apiMethod: 'GET' }"
|
|
697
|
+
item-title="nama"
|
|
698
|
+
item-value="id"
|
|
699
|
+
clearable
|
|
700
|
+
/>
|
|
701
|
+
</template>
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
> Request yang dikirim: `GET /api/master/departemen?page=1&perPage=100`
|
|
705
|
+
> Base URL diambil otomatis dari konfigurasi host project (`apiBaseUrl`).
|
|
706
|
+
|
|
707
|
+
#### Dengan Parameter Tambahan
|
|
708
|
+
|
|
709
|
+
```vue
|
|
710
|
+
<template>
|
|
711
|
+
<DCodeAutoComplete
|
|
712
|
+
v-model="selectedId"
|
|
713
|
+
label="Pilih Jabatan"
|
|
714
|
+
:endpoint="{ apiUrl: '/api/master/jabatan' }"
|
|
715
|
+
:apiParams="{ idDepartemen: selectedDeptId, isActive: true }"
|
|
716
|
+
item-title="namaJabatan"
|
|
717
|
+
item-value="idJabatan"
|
|
718
|
+
/>
|
|
719
|
+
</template>
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
> Request yang dikirim: `GET /api/master/jabatan?page=1&perPage=100&idDepartemen=5&isActive=true`
|
|
723
|
+
|
|
724
|
+
#### Override Default Pagination
|
|
725
|
+
|
|
726
|
+
```vue
|
|
727
|
+
<template>
|
|
728
|
+
<!-- Ganti perPage menjadi 50 -->
|
|
729
|
+
<DCodeAutoComplete
|
|
730
|
+
v-model="selected"
|
|
731
|
+
label="Pilih Data"
|
|
732
|
+
:endpoint="{ apiUrl: '/api/data' }"
|
|
733
|
+
:apiParams="{ perPage: 50 }"
|
|
734
|
+
item-title="nama"
|
|
735
|
+
item-value="id"
|
|
736
|
+
/>
|
|
737
|
+
</template>
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
#### Dengan Data Statis (tanpa API)
|
|
741
|
+
|
|
742
|
+
```vue
|
|
743
|
+
<template>
|
|
744
|
+
<DCodeAutoComplete
|
|
745
|
+
v-model="selected"
|
|
746
|
+
label="Pilih Status"
|
|
747
|
+
:items="[
|
|
748
|
+
{ id: 1, name: 'Aktif' },
|
|
749
|
+
{ id: 0, name: 'Nonaktif' },
|
|
750
|
+
]"
|
|
751
|
+
item-title="name"
|
|
752
|
+
item-value="id"
|
|
753
|
+
/>
|
|
754
|
+
</template>
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
#### Return Object Penuh
|
|
758
|
+
|
|
759
|
+
```vue
|
|
760
|
+
<script setup>
|
|
761
|
+
import { ref } from 'vue'
|
|
762
|
+
|
|
763
|
+
const selectedUser = ref(null) // akan berisi { id: 1, name: 'John', email: '...' }
|
|
764
|
+
</script>
|
|
765
|
+
|
|
766
|
+
<template>
|
|
767
|
+
<DCodeAutoComplete
|
|
768
|
+
v-model="selectedUser"
|
|
769
|
+
label="Pilih User"
|
|
770
|
+
:endpoint="{ apiUrl: '/api/users' }"
|
|
771
|
+
item-title="name"
|
|
772
|
+
item-value="id"
|
|
773
|
+
return-object
|
|
774
|
+
/>
|
|
775
|
+
</template>
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Format Response API yang Diharapkan
|
|
779
|
+
|
|
780
|
+
```json
|
|
781
|
+
{
|
|
782
|
+
"status": true,
|
|
783
|
+
"data": {
|
|
784
|
+
"data": [
|
|
785
|
+
{ "id": 1, "name": "Item A" },
|
|
786
|
+
{ "id": 2, "name": "Item B" }
|
|
787
|
+
]
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
Atau format flat:
|
|
793
|
+
|
|
794
|
+
```json
|
|
795
|
+
{
|
|
796
|
+
"data": [
|
|
797
|
+
{ "id": 1, "name": "Item A" }
|
|
798
|
+
]
|
|
799
|
+
}
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
Atau array langsung:
|
|
803
|
+
|
|
804
|
+
```json
|
|
805
|
+
[
|
|
806
|
+
{ "id": 1, "name": "Item A" }
|
|
807
|
+
]
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
## Web Components (Framework Agnostic)
|
|
813
|
+
|
|
814
|
+
Untuk digunakan di **React, Angular, Svelte**, atau framework lainnya, gunakan mode Web Components.
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## API Referensi Web Components
|
|
819
|
+
|
|
820
|
+
### Web Components
|
|
821
|
+
|
|
822
|
+
Setelah library di-import, dua custom element berikut akan terdaftar secara global:
|
|
823
|
+
|
|
824
|
+
| Custom Element | Deskripsi |
|
|
825
|
+
| ------------------------- | ----------------------------------------------------------- |
|
|
826
|
+
| `<micro-dynamic-form>` | Render form dinamis berdasarkan JSON config `config` prop. |
|
|
827
|
+
| `<micro-dynamic-datatable>` | Render tabel dinamis berdasarkan JSON config `config` prop. |
|
|
828
|
+
| `<micro-data-table>` | Render Data Table base component secara independen. Menerima props seperti `columns`, `data`, `apiUrl`. |
|
|
829
|
+
|
|
830
|
+
#### Properties (set via JavaScript/DOM)
|
|
831
|
+
|
|
832
|
+
Karena Web Components menerima objek JavaScript (bukan string HTML attribute), set properti ini **secara imperatif via DOM** atau `ref`:
|
|
833
|
+
|
|
834
|
+
| Property | Tipe | Deskripsi |
|
|
835
|
+
| --------- | -------- | --------------------------------------- |
|
|
836
|
+
| `config` | `Object` | Konfigurasi form / tabel dalam bentuk JSON. |
|
|
837
|
+
|
|
838
|
+
#### Events
|
|
839
|
+
|
|
840
|
+
| Event | `event.detail` | Deskripsi |
|
|
841
|
+
| -------- | ------------------ | ------------------------------------------- |
|
|
842
|
+
| `submit` | `Object` (payload) | Dipanggil saat form di-submit. |
|
|
843
|
+
| `action` | `Object` (payload) | Dipanggil saat action button datatable diklik. |
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
### Logic Registry API
|
|
848
|
+
|
|
849
|
+
Digunakan untuk menyuntikkan business logic dari aplikasi host ke dalam komponen.
|
|
850
|
+
|
|
851
|
+
```js
|
|
852
|
+
import {
|
|
853
|
+
registerLogic,
|
|
854
|
+
unregisterLogic,
|
|
855
|
+
getLogicModule
|
|
856
|
+
} from '@gemafajarramadhan/dynamic-ui'
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
| Fungsi | Deskripsi |
|
|
860
|
+
| ----------------------------- | ----------------------------------------------------------- |
|
|
861
|
+
| `registerLogic(code, module)` | Daftarkan logic module untuk layout code tertentu. |
|
|
862
|
+
| `unregisterLogic(code)` | Hapus logic module dari registry. |
|
|
863
|
+
| `getLogicModule(code)` | Ambil logic module berdasarkan layout code. |
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## Penggunaan di Berbagai Framework
|
|
868
|
+
|
|
869
|
+
---
|
|
870
|
+
|
|
871
|
+
### 1. Vanilla HTML / JavaScript
|
|
872
|
+
|
|
873
|
+
Cara paling sederhana. Import library dari bundler, lalu gunakan custom element di HTML.
|
|
874
|
+
|
|
875
|
+
```html
|
|
876
|
+
<!DOCTYPE html>
|
|
877
|
+
<html lang="en">
|
|
878
|
+
<head>
|
|
879
|
+
<meta charset="UTF-8" />
|
|
880
|
+
<title>Dynamic UI</title>
|
|
881
|
+
<!-- Import style -->
|
|
882
|
+
<link rel="stylesheet" href="./node_modules/@gemafajarramadhan/dynamic-ui/dist/style.css" />
|
|
883
|
+
</head>
|
|
884
|
+
<body>
|
|
885
|
+
|
|
886
|
+
<!-- Dynamic Form -->
|
|
887
|
+
<micro-dynamic-form id="myForm"></micro-dynamic-form>
|
|
888
|
+
|
|
889
|
+
<!-- Dynamic DataTable (AutoLayout version) -->
|
|
890
|
+
<micro-dynamic-datatable id="myAutoTable"></micro-dynamic-datatable>
|
|
891
|
+
|
|
892
|
+
<!-- Base DataTable (Stand-alone version) -->
|
|
893
|
+
<micro-data-table id="myBaseTable"></micro-data-table>
|
|
894
|
+
|
|
895
|
+
<!-- Import library (registrasi Web Components otomatis) -->
|
|
896
|
+
<script type="module">
|
|
897
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
898
|
+
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
899
|
+
|
|
900
|
+
// ✅ Konfigurasi API dari host project
|
|
901
|
+
setApiConfig({
|
|
902
|
+
apiBaseUrl: 'https://api.example.com',
|
|
903
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
// --- Setup Form ---
|
|
907
|
+
const form = document.querySelector('#myForm')
|
|
908
|
+
form.config = {
|
|
909
|
+
title: 'Registrasi User',
|
|
910
|
+
sections: [
|
|
911
|
+
{
|
|
912
|
+
title: 'Info Pribadi',
|
|
913
|
+
fields: [
|
|
914
|
+
{
|
|
915
|
+
key: 'name',
|
|
916
|
+
label: 'Nama Lengkap',
|
|
917
|
+
model: 'fullName',
|
|
918
|
+
component: 'DCodeTextField',
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
key: 'email',
|
|
922
|
+
label: 'Email',
|
|
923
|
+
model: 'email',
|
|
924
|
+
component: 'DCodeTextField',
|
|
925
|
+
props: { type: 'email' },
|
|
926
|
+
},
|
|
927
|
+
],
|
|
928
|
+
},
|
|
929
|
+
],
|
|
930
|
+
actions: [{ key: 'submit', label: 'Daftar', component: 'DCodeButton' }],
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
form.addEventListener('submit', (e) => {
|
|
934
|
+
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
// --- Setup DataTable ---
|
|
938
|
+
const autoTable = document.querySelector('#myAutoTable')
|
|
939
|
+
autoTable.config = {
|
|
940
|
+
titleID: 'Daftar User',
|
|
941
|
+
headers: [
|
|
942
|
+
{ text: 'Nama', value: 'name' },
|
|
943
|
+
{ text: 'Email', value: 'email' },
|
|
944
|
+
],
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
autoTable.addEventListener('action', (e) => {
|
|
948
|
+
|
|
949
|
+
})
|
|
950
|
+
|
|
951
|
+
// --- Setup Base MicroDataTable ---
|
|
952
|
+
const baseTable = document.querySelector('#myBaseTable')
|
|
953
|
+
// Untuk MicroDataTable (base component), Anda bisa mengirimkan prop langsung:
|
|
954
|
+
baseTable.columns = [
|
|
955
|
+
{ key: 'name', label: 'Nama', sortable: true },
|
|
956
|
+
{ key: 'email', label: 'Email' }
|
|
957
|
+
]
|
|
958
|
+
// Memberikan data secara statis
|
|
959
|
+
baseTable.data = [
|
|
960
|
+
{ name: 'John Doe', email: 'john@example.com' },
|
|
961
|
+
{ name: 'Jane Smith', email: 'jane@example.com' }
|
|
962
|
+
]
|
|
963
|
+
|
|
964
|
+
// Atau bisa diset melalui API:
|
|
965
|
+
// baseTable.apiUrl = '/api/users'
|
|
966
|
+
// baseTable.apiMethod = 'GET'
|
|
967
|
+
</script>
|
|
968
|
+
|
|
969
|
+
</body>
|
|
970
|
+
</html>
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
### 2. Vue 3 (Web Components)
|
|
976
|
+
|
|
977
|
+
Di Vue 3, Web Components bisa dipakai langsung seperti HTML element biasa. Gunakan `ref` untuk set properti objek.
|
|
978
|
+
|
|
979
|
+
#### Konfigurasi Vite
|
|
980
|
+
|
|
981
|
+
Agar Vue tidak menganggap `micro-dynamic-*` sebagai Vue component yang belum terdaftar, tambahkan `isCustomElement` di `vite.config.js`:
|
|
982
|
+
|
|
983
|
+
```js
|
|
984
|
+
// vite.config.js
|
|
985
|
+
import { defineConfig } from 'vite'
|
|
986
|
+
import vue from '@vitejs/plugin-vue'
|
|
987
|
+
|
|
988
|
+
export default defineConfig({
|
|
989
|
+
plugins: [
|
|
990
|
+
vue({
|
|
991
|
+
template: {
|
|
992
|
+
compilerOptions: {
|
|
993
|
+
// Semua element yang dimulai dengan 'micro-' dianggap custom element
|
|
994
|
+
isCustomElement: (tag) => tag.startsWith('micro-'),
|
|
995
|
+
},
|
|
996
|
+
},
|
|
997
|
+
}),
|
|
998
|
+
],
|
|
999
|
+
})
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
#### Entry Point (`main.js`)
|
|
1003
|
+
|
|
1004
|
+
```js
|
|
1005
|
+
// main.js
|
|
1006
|
+
import { createApp } from 'vue'
|
|
1007
|
+
import App from './App.vue'
|
|
1008
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
1009
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
1010
|
+
|
|
1011
|
+
const app = createApp(App)
|
|
1012
|
+
|
|
1013
|
+
// ✅ Konfigurasi API dari env host project
|
|
1014
|
+
app.use(DynamicUI, {
|
|
1015
|
+
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
|
|
1016
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
1017
|
+
onUnauthorized: () => router.push('/login'),
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
app.mount('#app')
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
#### Komponen Vue (`MyForm.vue`)
|
|
1024
|
+
|
|
1025
|
+
```vue
|
|
1026
|
+
<script setup>
|
|
1027
|
+
import { ref, onMounted } from 'vue'
|
|
1028
|
+
|
|
1029
|
+
const formRef = ref(null)
|
|
1030
|
+
|
|
1031
|
+
const formConfig = {
|
|
1032
|
+
title: 'Form User',
|
|
1033
|
+
sections: [
|
|
1034
|
+
{
|
|
1035
|
+
fields: [
|
|
1036
|
+
{
|
|
1037
|
+
key: 'username',
|
|
1038
|
+
label: 'Username',
|
|
1039
|
+
model: 'username',
|
|
1040
|
+
component: 'DCodeTextField',
|
|
1041
|
+
},
|
|
1042
|
+
],
|
|
1043
|
+
},
|
|
1044
|
+
],
|
|
1045
|
+
actions: [{ key: 'save', label: 'Simpan', component: 'DCodeButton' }],
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
onMounted(() => {
|
|
1049
|
+
if (formRef.value) {
|
|
1050
|
+
// Set config via DOM property (bukan attribute)
|
|
1051
|
+
formRef.value.config = formConfig
|
|
1052
|
+
|
|
1053
|
+
formRef.value.addEventListener('submit', (e) => {
|
|
1054
|
+
|
|
1055
|
+
})
|
|
1056
|
+
}
|
|
1057
|
+
})
|
|
1058
|
+
</script>
|
|
1059
|
+
|
|
1060
|
+
<template>
|
|
1061
|
+
<micro-dynamic-form ref="formRef" />
|
|
1062
|
+
</template>
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
---
|
|
1066
|
+
|
|
1067
|
+
### 3. React
|
|
1068
|
+
|
|
1069
|
+
Di React, gunakan `useRef` dan `useEffect` untuk set properti DOM secara imperatif karena React tidak meneruskan objek langsung ke Web Component attributes.
|
|
1070
|
+
|
|
1071
|
+
#### Entry Point (`main.jsx`)
|
|
1072
|
+
|
|
1073
|
+
```jsx
|
|
1074
|
+
// main.jsx
|
|
1075
|
+
import React from 'react'
|
|
1076
|
+
import ReactDOM from 'react-dom/client'
|
|
1077
|
+
import App from './App'
|
|
1078
|
+
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
1079
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
1080
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
1081
|
+
|
|
1082
|
+
// ✅ Konfigurasi API dari env host project
|
|
1083
|
+
setApiConfig({
|
|
1084
|
+
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
|
|
1085
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
1086
|
+
onUnauthorized: () => { window.location.href = '/login' },
|
|
1087
|
+
})
|
|
1088
|
+
|
|
1089
|
+
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
#### Dynamic Form Component
|
|
1093
|
+
|
|
1094
|
+
```jsx
|
|
1095
|
+
// components/DynamicForm.jsx
|
|
1096
|
+
import React, { useEffect, useRef } from 'react'
|
|
1097
|
+
|
|
1098
|
+
const formConfig = {
|
|
1099
|
+
title: 'Form via React',
|
|
1100
|
+
sections: [
|
|
1101
|
+
{
|
|
1102
|
+
fields: [
|
|
1103
|
+
{
|
|
1104
|
+
key: 'username',
|
|
1105
|
+
label: 'Username',
|
|
1106
|
+
model: 'username',
|
|
1107
|
+
component: 'DCodeTextField',
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
key: 'email',
|
|
1111
|
+
label: 'Email',
|
|
1112
|
+
model: 'email',
|
|
1113
|
+
component: 'DCodeTextField',
|
|
1114
|
+
props: { type: 'email' },
|
|
1115
|
+
},
|
|
1116
|
+
],
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
actions: [{ key: 'submit', label: 'Kirim', component: 'DCodeButton' }],
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
export default function DynamicForm() {
|
|
1123
|
+
const formRef = useRef(null)
|
|
1124
|
+
|
|
1125
|
+
useEffect(() => {
|
|
1126
|
+
const el = formRef.current
|
|
1127
|
+
if (!el) return
|
|
1128
|
+
|
|
1129
|
+
// Set config sebagai DOM property
|
|
1130
|
+
el.config = formConfig
|
|
1131
|
+
|
|
1132
|
+
const handleSubmit = (event) => {
|
|
1133
|
+
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
el.addEventListener('submit', handleSubmit)
|
|
1137
|
+
|
|
1138
|
+
// Cleanup listener saat unmount
|
|
1139
|
+
return () => {
|
|
1140
|
+
el.removeEventListener('submit', handleSubmit)
|
|
1141
|
+
}
|
|
1142
|
+
}, [])
|
|
1143
|
+
|
|
1144
|
+
return <micro-dynamic-form ref={formRef}></micro-dynamic-form>
|
|
1145
|
+
}
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
#### Dynamic DataTable Component
|
|
1149
|
+
|
|
1150
|
+
```jsx
|
|
1151
|
+
// components/DynamicTable.jsx
|
|
1152
|
+
import React, { useEffect, useRef } from 'react'
|
|
1153
|
+
|
|
1154
|
+
const tableConfig = {
|
|
1155
|
+
titleID: 'Daftar User',
|
|
1156
|
+
headers: [
|
|
1157
|
+
{ text: 'Nama', value: 'name' },
|
|
1158
|
+
{ text: 'Email', value: 'email' },
|
|
1159
|
+
{ text: 'Status', value: 'status' },
|
|
1160
|
+
],
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
export default function DynamicTable() {
|
|
1164
|
+
const tableRef = useRef(null)
|
|
1165
|
+
|
|
1166
|
+
useEffect(() => {
|
|
1167
|
+
const el = tableRef.current
|
|
1168
|
+
if (!el) return
|
|
1169
|
+
|
|
1170
|
+
el.config = tableConfig
|
|
1171
|
+
|
|
1172
|
+
const handleAction = (event) => {
|
|
1173
|
+
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
el.addEventListener('action', handleAction)
|
|
1177
|
+
|
|
1178
|
+
return () => {
|
|
1179
|
+
el.removeEventListener('action', handleAction)
|
|
1180
|
+
}
|
|
1181
|
+
}, [])
|
|
1182
|
+
|
|
1183
|
+
return <micro-dynamic-datatable ref={tableRef}></micro-dynamic-datatable>
|
|
1184
|
+
}
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
#### TypeScript + React
|
|
1188
|
+
|
|
1189
|
+
Tambahkan deklarasi tipe agar JSX tidak mengeluh tentang custom element:
|
|
1190
|
+
|
|
1191
|
+
```ts
|
|
1192
|
+
// src/types/dynamic-ui.d.ts
|
|
1193
|
+
import React from 'react'
|
|
1194
|
+
|
|
1195
|
+
declare global {
|
|
1196
|
+
namespace JSX {
|
|
1197
|
+
interface IntrinsicElements {
|
|
1198
|
+
'micro-dynamic-form': React.DetailedHTMLProps<
|
|
1199
|
+
React.HTMLAttributes<HTMLElement>,
|
|
1200
|
+
HTMLElement
|
|
1201
|
+
>
|
|
1202
|
+
'micro-dynamic-datatable': React.DetailedHTMLProps<
|
|
1203
|
+
React.HTMLAttributes<HTMLElement>,
|
|
1204
|
+
HTMLElement
|
|
1205
|
+
>
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
---
|
|
1212
|
+
|
|
1213
|
+
### 4. Next.js (App Router)
|
|
1214
|
+
|
|
1215
|
+
Di Next.js dengan App Router, Web Components harus di-import di sisi klien (`'use client'`).
|
|
1216
|
+
|
|
1217
|
+
#### Setup Konfigurasi API
|
|
1218
|
+
|
|
1219
|
+
```ts
|
|
1220
|
+
// lib/dynamic-ui-config.ts
|
|
1221
|
+
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
1222
|
+
|
|
1223
|
+
setApiConfig({
|
|
1224
|
+
apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL!,
|
|
1225
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
1226
|
+
onUnauthorized: () => { window.location.href = '/login' },
|
|
1227
|
+
})
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
#### Setup di Layout atau Client Component
|
|
1231
|
+
|
|
1232
|
+
```tsx
|
|
1233
|
+
// components/DynamicForm.tsx
|
|
1234
|
+
'use client'
|
|
1235
|
+
|
|
1236
|
+
import { useEffect, useRef } from 'react'
|
|
1237
|
+
import '@/lib/dynamic-ui-config'
|
|
1238
|
+
import('@gemafajarramadhan/dynamic-ui')
|
|
1239
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
1240
|
+
|
|
1241
|
+
const formConfig = {
|
|
1242
|
+
title: 'Form Next.js',
|
|
1243
|
+
sections: [
|
|
1244
|
+
{
|
|
1245
|
+
fields: [
|
|
1246
|
+
{
|
|
1247
|
+
key: 'name',
|
|
1248
|
+
label: 'Nama',
|
|
1249
|
+
model: 'name',
|
|
1250
|
+
component: 'DCodeTextField',
|
|
1251
|
+
},
|
|
1252
|
+
],
|
|
1253
|
+
},
|
|
1254
|
+
],
|
|
1255
|
+
actions: [{ key: 'submit', label: 'Submit', component: 'DCodeButton' }],
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export default function DynamicFormClient() {
|
|
1259
|
+
const formRef = useRef<HTMLElement>(null)
|
|
1260
|
+
|
|
1261
|
+
useEffect(() => {
|
|
1262
|
+
const el = formRef.current
|
|
1263
|
+
if (!el) return
|
|
1264
|
+
|
|
1265
|
+
;(el as any).config = formConfig
|
|
1266
|
+
|
|
1267
|
+
const handleSubmit = (event: Event) => {
|
|
1268
|
+
const customEvent = event as CustomEvent
|
|
1269
|
+
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
el.addEventListener('submit', handleSubmit)
|
|
1273
|
+
return () => el.removeEventListener('submit', handleSubmit)
|
|
1274
|
+
}, [])
|
|
1275
|
+
|
|
1276
|
+
return (
|
|
1277
|
+
// @ts-ignore – custom element tidak dikenal TypeScript secara default
|
|
1278
|
+
<micro-dynamic-form ref={formRef}></micro-dynamic-form>
|
|
1279
|
+
)
|
|
1280
|
+
}
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
#### Penggunaan di Page
|
|
1284
|
+
|
|
1285
|
+
```tsx
|
|
1286
|
+
// app/page.tsx
|
|
1287
|
+
import DynamicFormClient from '@/components/DynamicForm'
|
|
1288
|
+
|
|
1289
|
+
export default function HomePage() {
|
|
1290
|
+
return (
|
|
1291
|
+
<main>
|
|
1292
|
+
<h1>Dynamic Form</h1>
|
|
1293
|
+
<DynamicFormClient />
|
|
1294
|
+
</main>
|
|
1295
|
+
)
|
|
1296
|
+
}
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
> **Catatan:** Gunakan `dynamic()` dari `next/dynamic` dengan `{ ssr: false }` jika terjadi error SSR.
|
|
1300
|
+
>
|
|
1301
|
+
> ```tsx
|
|
1302
|
+
> import dynamic from 'next/dynamic'
|
|
1303
|
+
> const DynamicFormClient = dynamic(() => import('@/components/DynamicForm'), { ssr: false })
|
|
1304
|
+
> ```
|
|
1305
|
+
|
|
1306
|
+
> File `.env.local` di host project:
|
|
1307
|
+
> ```env
|
|
1308
|
+
> NEXT_PUBLIC_API_BASE_URL=https://api.example.com
|
|
1309
|
+
> ```
|
|
1310
|
+
|
|
1311
|
+
---
|
|
1312
|
+
|
|
1313
|
+
### 5. Angular
|
|
1314
|
+
|
|
1315
|
+
Di Angular, tambahkan `CUSTOM_ELEMENTS_SCHEMA` agar Angular tidak melempar error pada custom element yang tidak dikenali.
|
|
1316
|
+
|
|
1317
|
+
#### `app.module.ts` (Module-based)
|
|
1318
|
+
|
|
1319
|
+
```ts
|
|
1320
|
+
// app.module.ts
|
|
1321
|
+
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
1322
|
+
import { BrowserModule } from '@angular/platform-browser'
|
|
1323
|
+
import { AppComponent } from './app.component'
|
|
1324
|
+
|
|
1325
|
+
@NgModule({
|
|
1326
|
+
declarations: [AppComponent],
|
|
1327
|
+
imports: [BrowserModule],
|
|
1328
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA], // <-- Wajib ditambahkan
|
|
1329
|
+
bootstrap: [AppComponent],
|
|
1330
|
+
})
|
|
1331
|
+
export class AppModule {}
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
#### `main.ts`
|
|
1335
|
+
|
|
1336
|
+
```ts
|
|
1337
|
+
// main.ts
|
|
1338
|
+
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
|
|
1339
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
1340
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
1341
|
+
|
|
1342
|
+
// ✅ Konfigurasi API dari environment Angular
|
|
1343
|
+
import { environment } from './environments/environment'
|
|
1344
|
+
|
|
1345
|
+
setApiConfig({
|
|
1346
|
+
apiBaseUrl: environment.apiBaseUrl,
|
|
1347
|
+
getAuthToken: () => localStorage.getItem('access_token'),
|
|
1348
|
+
onUnauthorized: () => { window.location.href = '/login' },
|
|
1349
|
+
})
|
|
1350
|
+
|
|
1351
|
+
platformBrowserDynamic().bootstrapModule(AppModule)
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
> File `src/environments/environment.ts`:
|
|
1355
|
+
> ```ts
|
|
1356
|
+
> export const environment = {
|
|
1357
|
+
> production: false,
|
|
1358
|
+
> apiBaseUrl: 'https://api.example.com',
|
|
1359
|
+
> }
|
|
1360
|
+
> ```
|
|
1361
|
+
|
|
1362
|
+
#### Komponen Angular (`dynamic-form.component.ts`)
|
|
1363
|
+
|
|
1364
|
+
```ts
|
|
1365
|
+
// dynamic-form.component.ts
|
|
1366
|
+
import {
|
|
1367
|
+
Component,
|
|
1368
|
+
OnInit,
|
|
1369
|
+
AfterViewInit,
|
|
1370
|
+
ViewChild,
|
|
1371
|
+
ElementRef,
|
|
1372
|
+
} from '@angular/core'
|
|
1373
|
+
|
|
1374
|
+
@Component({
|
|
1375
|
+
selector: 'app-dynamic-form',
|
|
1376
|
+
template: `<micro-dynamic-form #formEl></micro-dynamic-form>`,
|
|
1377
|
+
})
|
|
1378
|
+
export class DynamicFormComponent implements AfterViewInit {
|
|
1379
|
+
@ViewChild('formEl') formRef!: ElementRef
|
|
1380
|
+
|
|
1381
|
+
private formConfig = {
|
|
1382
|
+
title: 'Form Angular',
|
|
1383
|
+
sections: [
|
|
1384
|
+
{
|
|
1385
|
+
fields: [
|
|
1386
|
+
{
|
|
1387
|
+
key: 'username',
|
|
1388
|
+
label: 'Username',
|
|
1389
|
+
model: 'username',
|
|
1390
|
+
component: 'DCodeTextField',
|
|
1391
|
+
},
|
|
1392
|
+
],
|
|
1393
|
+
},
|
|
1394
|
+
],
|
|
1395
|
+
actions: [{ key: 'submit', label: 'Simpan', component: 'DCodeButton' }],
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
ngAfterViewInit() {
|
|
1399
|
+
const el = this.formRef.nativeElement
|
|
1400
|
+
|
|
1401
|
+
// Set config via DOM property
|
|
1402
|
+
el.config = this.formConfig
|
|
1403
|
+
|
|
1404
|
+
el.addEventListener('submit', (event: CustomEvent) => {
|
|
1405
|
+
|
|
1406
|
+
})
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
#### Angular Standalone (Angular 17+)
|
|
1412
|
+
|
|
1413
|
+
```ts
|
|
1414
|
+
// app.component.ts
|
|
1415
|
+
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'
|
|
1416
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
1417
|
+
|
|
1418
|
+
@Component({
|
|
1419
|
+
selector: 'app-root',
|
|
1420
|
+
standalone: true,
|
|
1421
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
1422
|
+
template: `<micro-dynamic-form #formEl></micro-dynamic-form>`,
|
|
1423
|
+
})
|
|
1424
|
+
export class AppComponent implements AfterViewInit {
|
|
1425
|
+
@ViewChild('formEl') formRef!: ElementRef
|
|
1426
|
+
|
|
1427
|
+
ngAfterViewInit() {
|
|
1428
|
+
const el = this.formRef.nativeElement
|
|
1429
|
+
el.config = { /* config Anda di sini */ }
|
|
1430
|
+
el.addEventListener('submit', (e: CustomEvent) =>
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
---
|
|
1436
|
+
|
|
1437
|
+
### 6. Nuxt 3
|
|
1438
|
+
|
|
1439
|
+
Di Nuxt 3, gunakan plugin untuk mendaftarkan Web Components dan tandai sebagai custom element.
|
|
1440
|
+
|
|
1441
|
+
#### Plugin (`plugins/dynamic-ui.client.ts`)
|
|
1442
|
+
|
|
1443
|
+
Buat file plugin dengan suffix `.client.ts` agar hanya dijalankan di sisi klien (browser):
|
|
1444
|
+
|
|
1445
|
+
```ts
|
|
1446
|
+
// plugins/dynamic-ui.client.ts
|
|
1447
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
1448
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
1449
|
+
|
|
1450
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
1451
|
+
const config = useRuntimeConfig()
|
|
1452
|
+
|
|
1453
|
+
// ✅ Konfigurasi API dari runtimeConfig Nuxt (env host project)
|
|
1454
|
+
nuxtApp.vueApp.use(DynamicUI, {
|
|
1455
|
+
apiBaseUrl: config.public.apiBaseUrl,
|
|
1456
|
+
getAuthToken: () => useCookie('access_token').value ?? null,
|
|
1457
|
+
onUnauthorized: () => navigateTo('/login'),
|
|
1458
|
+
})
|
|
1459
|
+
})
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
#### Konfigurasi Nuxt (`nuxt.config.ts`)
|
|
1463
|
+
|
|
1464
|
+
```ts
|
|
1465
|
+
// nuxt.config.ts
|
|
1466
|
+
export default defineNuxtConfig({
|
|
1467
|
+
runtimeConfig: {
|
|
1468
|
+
public: {
|
|
1469
|
+
apiBaseUrl: process.env.VITE_API_BASE_URL, // set di .env
|
|
1470
|
+
},
|
|
1471
|
+
},
|
|
1472
|
+
vue: {
|
|
1473
|
+
compilerOptions: {
|
|
1474
|
+
// Arahkan Nuxt/Vue agar tidak menganggap element ini sebagai Vue component
|
|
1475
|
+
isCustomElement: (tag: string) => tag.startsWith('micro-'),
|
|
1476
|
+
},
|
|
1477
|
+
},
|
|
1478
|
+
})
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
#### Penggunaan di Page atau Component
|
|
1482
|
+
|
|
1483
|
+
```vue
|
|
1484
|
+
<!-- pages/index.vue -->
|
|
1485
|
+
<script setup>
|
|
1486
|
+
import { ref, onMounted } from 'vue'
|
|
1487
|
+
|
|
1488
|
+
const formRef = ref(null)
|
|
1489
|
+
|
|
1490
|
+
const formConfig = {
|
|
1491
|
+
title: 'Form Nuxt',
|
|
1492
|
+
sections: [
|
|
1493
|
+
{
|
|
1494
|
+
fields: [
|
|
1495
|
+
{
|
|
1496
|
+
key: 'name',
|
|
1497
|
+
label: 'Nama',
|
|
1498
|
+
model: 'name',
|
|
1499
|
+
component: 'DCodeTextField',
|
|
1500
|
+
},
|
|
1501
|
+
],
|
|
1502
|
+
},
|
|
1503
|
+
],
|
|
1504
|
+
actions: [{ key: 'submit', label: 'Submit', component: 'DCodeButton' }],
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
onMounted(() => {
|
|
1508
|
+
if (formRef.value) {
|
|
1509
|
+
formRef.value.config = formConfig
|
|
1510
|
+
formRef.value.addEventListener('submit', (e) => {
|
|
1511
|
+
|
|
1512
|
+
})
|
|
1513
|
+
}
|
|
1514
|
+
})
|
|
1515
|
+
</script>
|
|
1516
|
+
|
|
1517
|
+
<template>
|
|
1518
|
+
<div>
|
|
1519
|
+
<h1>Halaman Dynamic Form</h1>
|
|
1520
|
+
<!-- Gunakan ClientOnly agar tidak error saat SSR -->
|
|
1521
|
+
<ClientOnly>
|
|
1522
|
+
<micro-dynamic-form ref="formRef" />
|
|
1523
|
+
</ClientOnly>
|
|
1524
|
+
</div>
|
|
1525
|
+
</template>
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
---
|
|
1529
|
+
|
|
1530
|
+
## Custom Logic Registry
|
|
1531
|
+
|
|
1532
|
+
Library menyediakan **Logic Registry** agar aplikasi host dapat menyuntikkan business logic ke dalam komponen tanpa memodifikasi library.
|
|
1533
|
+
|
|
1534
|
+
### Option A: via `window` (sebelum library dimuat)
|
|
1535
|
+
|
|
1536
|
+
```js
|
|
1537
|
+
window.__MICRO_LOGIC_REGISTRY__ = {
|
|
1538
|
+
'user-management': {
|
|
1539
|
+
onReady(context) {
|
|
1540
|
+
|
|
1541
|
+
},
|
|
1542
|
+
handleAction(action, payload) {
|
|
1543
|
+
if (action === 'delete') {
|
|
1544
|
+
// Tampilkan modal konfirmasi
|
|
1545
|
+
return true // intercept action
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
},
|
|
1549
|
+
}
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
### Option B: via fungsi `registerLogic` (setelah import library)
|
|
1553
|
+
|
|
1554
|
+
```js
|
|
1555
|
+
import {
|
|
1556
|
+
registerLogic,
|
|
1557
|
+
unregisterLogic,
|
|
1558
|
+
} from '@gemafajarramadhan/dynamic-ui'
|
|
1559
|
+
|
|
1560
|
+
registerLogic('user-management', {
|
|
1561
|
+
onReady(context) {
|
|
1562
|
+
|
|
1563
|
+
},
|
|
1564
|
+
handleAction(action, payload) {
|
|
1565
|
+
|
|
1566
|
+
},
|
|
1567
|
+
})
|
|
1568
|
+
|
|
1569
|
+
// Untuk menghapus logic saat komponen unmount:
|
|
1570
|
+
// unregisterLogic('user-management')
|
|
1571
|
+
```
|
|
1572
|
+
|
|
1573
|
+
### Interface LogicModule
|
|
1574
|
+
|
|
1575
|
+
```ts
|
|
1576
|
+
interface LogicModule {
|
|
1577
|
+
onReady?: (context: Record<string, any>) => void
|
|
1578
|
+
handleAction?: (action: string, payload: any) => boolean | void
|
|
1579
|
+
}
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
---
|
|
1583
|
+
|
|
1584
|
+
## TypeScript Support
|
|
1585
|
+
|
|
1586
|
+
Untuk menambahkan type yang benar pada semua framework, buat file deklarasi global:
|
|
1587
|
+
|
|
1588
|
+
```ts
|
|
1589
|
+
// src/types/dynamic-ui.d.ts
|
|
1590
|
+
|
|
1591
|
+
declare namespace DynamicUI {
|
|
1592
|
+
interface LogicModule {
|
|
1593
|
+
onReady?: (context: Record<string, any>) => void
|
|
1594
|
+
handleAction?: (action: string, payload: any) => boolean | void
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Deklarasi untuk custom element di JSX (React / Next.js)
|
|
1599
|
+
declare global {
|
|
1600
|
+
namespace JSX {
|
|
1601
|
+
interface IntrinsicElements {
|
|
1602
|
+
'micro-dynamic-form': any
|
|
1603
|
+
'micro-dynamic-datatable': any
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// Untuk global window registry
|
|
1608
|
+
interface Window {
|
|
1609
|
+
__MICRO_LOGIC_REGISTRY__?: Record<string, DynamicUI.LogicModule>
|
|
1610
|
+
__MICRO_API_BASE_URL__?: string
|
|
1611
|
+
__MICRO_AUTH_TOKEN__?: string
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
export {}
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
---
|
|
1619
|
+
|
|
1620
|
+
## Referensi Props Komponen
|
|
1621
|
+
|
|
1622
|
+
Berikut adalah dokumentasi lengkap props untuk setiap komponen yang tersedia.
|
|
1623
|
+
|
|
1624
|
+
---
|
|
1625
|
+
|
|
1626
|
+
### `DataTable`
|
|
1627
|
+
|
|
1628
|
+
Komponen tabel data yang mendukung mode API (fetch otomatis) maupun data statis, dengan pagination, sorting, filtering, dan action button.
|
|
1629
|
+
|
|
1630
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1631
|
+
|------|------|---------|-----------|
|
|
1632
|
+
| `columns` | `TableColumn[]` | `[]` | Definisi kolom tabel |
|
|
1633
|
+
| `data` | `any[]` | `[]` | Data statis (mode non-API) |
|
|
1634
|
+
| `perPage` | `number` | `10` | Jumlah baris per halaman |
|
|
1635
|
+
| `loading` | `boolean` | `false` | Status loading eksternal |
|
|
1636
|
+
| `hasActions` | `boolean` | `false` | Tampilkan kolom aksi baris |
|
|
1637
|
+
| `nested` | `boolean` | `false` | Mode nested (tampilan kompak tanpa header search) |
|
|
1638
|
+
| `apiModule` | `string` | — | Module API untuk fetch data |
|
|
1639
|
+
| `apiAction` | `string` | `'list'` | Action API |
|
|
1640
|
+
| `apiMethod` | `string` | — | HTTP method (`GET`/`POST`) |
|
|
1641
|
+
| `apiUrl` | `string` | — | URL endpoint API langsung |
|
|
1642
|
+
| `apiParams` | `object` | `{}` | Parameter tambahan untuk request API |
|
|
1643
|
+
| `tableConfig` | `any` | — | Konfigurasi JSON lengkap tabel (dari backend) |
|
|
1644
|
+
| `dynamicColumns` | `boolean` | `false` | Gunakan kolom dinamis dari response API |
|
|
1645
|
+
| `enableQuickSearch` | `boolean` | `true` | Aktifkan pencarian cepat (debounce) |
|
|
1646
|
+
| `showPagination` | `boolean` | `true` | Tampilkan pagination |
|
|
1647
|
+
| `ignorePermissions` | `boolean` | `false` | Abaikan cek permission |
|
|
1648
|
+
| `permissionBase` | `string` | — | Base path untuk permission check |
|
|
1649
|
+
|
|
1650
|
+
```vue
|
|
1651
|
+
<template>
|
|
1652
|
+
<!-- Mode API -->
|
|
1653
|
+
<DataTable
|
|
1654
|
+
api-url="/api/users"
|
|
1655
|
+
api-method="POST"
|
|
1656
|
+
:columns="[
|
|
1657
|
+
{ key: 'name', label: 'Nama', sortable: true },
|
|
1658
|
+
{ key: 'email', label: 'Email' },
|
|
1659
|
+
]"
|
|
1660
|
+
:per-page="25"
|
|
1661
|
+
/>
|
|
1662
|
+
|
|
1663
|
+
<!-- Mode data statis -->
|
|
1664
|
+
<DataTable
|
|
1665
|
+
:data="myData"
|
|
1666
|
+
:columns="columns"
|
|
1667
|
+
/>
|
|
1668
|
+
</template>
|
|
1669
|
+
```
|
|
1670
|
+
|
|
1671
|
+
---
|
|
1672
|
+
|
|
1673
|
+
### `DCodeButton`
|
|
1674
|
+
|
|
1675
|
+
Tombol dengan berbagai variant, warna kustom, ikon Lucide, dan tooltip.
|
|
1676
|
+
|
|
1677
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1678
|
+
|------|------|---------|-----------|
|
|
1679
|
+
| `text` | `string` | `''` | Teks tombol |
|
|
1680
|
+
| `icon` | `string` | — | Nama ikon Lucide (e.g. `'Save'`, `'Trash2'`) |
|
|
1681
|
+
| `variant` | `'default'\|'secondary'\|'destructive'\|'outline'\|'ghost'\|'link'\|'tonal'\|'elevated'` | `'default'` | Variant tampilan |
|
|
1682
|
+
| `size` | `'default'\|'sm'\|'lg'\|'icon'` | `'default'` | Ukuran tombol |
|
|
1683
|
+
| `tooltip` | `string` | `''` | Teks tooltip |
|
|
1684
|
+
| `bgColor` | `string` | `''` | Warna background kustom (tailwind/hex) |
|
|
1685
|
+
| `textColor` | `string` | `''` | Warna teks kustom |
|
|
1686
|
+
| `hoverBgColor` | `string` | `''` | Warna background saat hover |
|
|
1687
|
+
| `borderColor` | `string` | `''` | Warna border kustom |
|
|
1688
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan tombol |
|
|
1689
|
+
| `type` | `'button'\|'submit'\|'reset'` | `'button'` | Tipe HTML button |
|
|
1690
|
+
| `visible` | `boolean` | `true` | Tampilkan/sembunyikan tombol |
|
|
1691
|
+
| `skeleton` | `boolean` | `false` | Tampilkan skeleton loading |
|
|
1692
|
+
|
|
1693
|
+
```vue
|
|
1694
|
+
<template>
|
|
1695
|
+
<DCodeButton text="Simpan" icon="Save" variant="default" />
|
|
1696
|
+
<DCodeButton text="Hapus" icon="Trash2" variant="destructive" tooltip="Hapus data ini" />
|
|
1697
|
+
<DCodeButton text="Submit" type="submit" :disabled="isLoading" />
|
|
1698
|
+
</template>
|
|
1699
|
+
```
|
|
1700
|
+
|
|
1701
|
+
---
|
|
1702
|
+
|
|
1703
|
+
### `DCodeTextField`
|
|
1704
|
+
|
|
1705
|
+
Input teks dengan banyak fitur: filter karakter, transformasi case, clearable, password toggle.
|
|
1706
|
+
|
|
1707
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1708
|
+
|------|------|---------|-----------|
|
|
1709
|
+
| `modelValue` | `string\|number\|null` | — | Value input (v-model) |
|
|
1710
|
+
| `prependInner` | `string` | — | Teks/ikon di sisi kiri input |
|
|
1711
|
+
| `clearable` | `boolean` | — | Tampilkan tombol clear |
|
|
1712
|
+
| `hideClearWhenEmpty` | `boolean` | — | Sembunyikan tombol clear saat kosong |
|
|
1713
|
+
| `textCase` | `'none'\|'uppercase'\|'lowercase'\|'titlecase'\|'sentencecase'` | `'none'` | Transformasi huruf |
|
|
1714
|
+
| `applyTextCaseToValue` | `boolean` | `true` | Terapkan transformasi ke v-model |
|
|
1715
|
+
| `valueType` | `'free'\|'number'\|'letter'\|'letternumber'\|'email'\|'password'` | `'free'` | Filter jenis karakter yang diperbolehkan |
|
|
1716
|
+
| `allowSpaces` | `boolean` | — | Izinkan spasi |
|
|
1717
|
+
| `allowChars` | `string\|string[]` | — | Karakter tambahan yang diizinkan |
|
|
1718
|
+
| `pattern` | `RegExp` | — | Regex validasi |
|
|
1719
|
+
| `visible` | `boolean` | `true` | Tampilkan/sembunyikan |
|
|
1720
|
+
| `skeleton` | `boolean` | `false` | Tampilkan skeleton |
|
|
1721
|
+
| `returnNumber` | `boolean` | — | Emit nilai sebagai number |
|
|
1722
|
+
| `error` | `string\|null` | — | Pesan error validasi |
|
|
1723
|
+
|
|
1724
|
+
```vue
|
|
1725
|
+
<template>
|
|
1726
|
+
<DCodeTextField
|
|
1727
|
+
v-model="form.name"
|
|
1728
|
+
label="Nama Lengkap"
|
|
1729
|
+
placeholder="Masukkan nama..."
|
|
1730
|
+
text-case="titlecase"
|
|
1731
|
+
clearable
|
|
1732
|
+
/>
|
|
1733
|
+
<DCodeTextField
|
|
1734
|
+
v-model="form.email"
|
|
1735
|
+
label="Email"
|
|
1736
|
+
value-type="email"
|
|
1737
|
+
:error="errors.email"
|
|
1738
|
+
/>
|
|
1739
|
+
<DCodeTextField
|
|
1740
|
+
v-model="form.password"
|
|
1741
|
+
label="Password"
|
|
1742
|
+
value-type="password"
|
|
1743
|
+
/>
|
|
1744
|
+
</template>
|
|
1745
|
+
```
|
|
1746
|
+
|
|
1747
|
+
---
|
|
1748
|
+
|
|
1749
|
+
### `DCodeTextarea`
|
|
1750
|
+
|
|
1751
|
+
Textarea dengan karakter counter, filter, dan transformasi case.
|
|
1752
|
+
|
|
1753
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1754
|
+
|------|------|---------|-----------|
|
|
1755
|
+
| `modelValue` | `string\|number\|null` | — | Value (v-model) |
|
|
1756
|
+
| `clearable` | `boolean` | — | Tombol clear |
|
|
1757
|
+
| `hideClearWhenEmpty` | `boolean` | `true` | Sembunyikan clear saat kosong |
|
|
1758
|
+
| `textCase` | `'none'\|'uppercase'\|'lowercase'\|'titlecase'\|'sentencecase'` | `'none'` | Transformasi huruf |
|
|
1759
|
+
| `applyTextCaseToValue` | `boolean` | `true` | Terapkan ke v-model |
|
|
1760
|
+
| `valueType` | `'free'\|'number'\|'letter'\|'letternumber'` | `'free'` | Filter karakter |
|
|
1761
|
+
| `allowSpaces` | `boolean` | `true` | Izinkan spasi |
|
|
1762
|
+
| `allowChars` | `string\|string[]` | — | Karakter tambahan |
|
|
1763
|
+
| `pattern` | `RegExp` | — | Regex validasi |
|
|
1764
|
+
| `visible` | `boolean` | `true` | Tampilkan/sembunyikan |
|
|
1765
|
+
| `skeleton` | `boolean` | `false` | Skeleton loading |
|
|
1766
|
+
| `showCharCount` | `boolean` | `true` | Tampilkan counter karakter |
|
|
1767
|
+
| `returnNumber` | `boolean` | `false` | Emit sebagai number |
|
|
1768
|
+
| `error` | `string\|null` | `null` | Pesan error |
|
|
1769
|
+
|
|
1770
|
+
```vue
|
|
1771
|
+
<template>
|
|
1772
|
+
<DCodeTextarea
|
|
1773
|
+
v-model="form.description"
|
|
1774
|
+
label="Deskripsi"
|
|
1775
|
+
placeholder="Masukkan deskripsi..."
|
|
1776
|
+
:show-char-count="true"
|
|
1777
|
+
maxlength="500"
|
|
1778
|
+
/>
|
|
1779
|
+
</template>
|
|
1780
|
+
```
|
|
1781
|
+
|
|
1782
|
+
---
|
|
1783
|
+
|
|
1784
|
+
### `DCodeAutoComplete`
|
|
1785
|
+
|
|
1786
|
+
Dropdown autocomplete dengan fetch API otomatis dan pencarian.
|
|
1787
|
+
|
|
1788
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1789
|
+
|------|------|---------|-----------|
|
|
1790
|
+
| `modelValue` | `any` | — | Value terpilih (v-model) |
|
|
1791
|
+
| `endpoint` | `{ apiUrl?, apiMethod?, apiModule?, apiAction? }` | — | Konfigurasi endpoint API |
|
|
1792
|
+
| `apiParams` | `object` | `{}` | Parameter tambahan request |
|
|
1793
|
+
| `fetchItems` | `Function` | — | Fungsi custom fetch (alternatif endpoint) |
|
|
1794
|
+
| `itemTitle` | `string\|Function` | `'name'` | Field label item |
|
|
1795
|
+
| `itemValue` | `string\|Function` | `'id'` | Field nilai item |
|
|
1796
|
+
| `returnObject` | `boolean` | `false` | Return objek penuh |
|
|
1797
|
+
| `autoFetchOnOpen` | `boolean` | `true` | Fetch saat dropdown terbuka |
|
|
1798
|
+
| `autoFetchOnMount` | `boolean` | `false` | Fetch saat mount |
|
|
1799
|
+
| `clearable` | `boolean` | `false` | Tombol clear |
|
|
1800
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
1801
|
+
| `visible` | `boolean` | `true` | Tampilkan/sembunyikan |
|
|
1802
|
+
| `skeleton` | `boolean` | `false` | Skeleton loading |
|
|
1803
|
+
| `error` | `string\|null` | `null` | Pesan error |
|
|
1804
|
+
| `icon` | `Component` | — | Ikon kiri input |
|
|
1805
|
+
|
|
1806
|
+
```vue
|
|
1807
|
+
<template>
|
|
1808
|
+
<DCodeAutoComplete
|
|
1809
|
+
v-model="form.departemenId"
|
|
1810
|
+
label="Departemen"
|
|
1811
|
+
:endpoint="{ apiUrl: '/api/master/departemen' }"
|
|
1812
|
+
item-title="nama"
|
|
1813
|
+
item-value="id"
|
|
1814
|
+
clearable
|
|
1815
|
+
/>
|
|
1816
|
+
</template>
|
|
1817
|
+
```
|
|
1818
|
+
|
|
1819
|
+
---
|
|
1820
|
+
|
|
1821
|
+
### `DCodeInfiniteAutoComplete`
|
|
1822
|
+
|
|
1823
|
+
Autocomplete dengan pagination infinite scroll (load more saat scroll ke bawah).
|
|
1824
|
+
|
|
1825
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1826
|
+
|------|------|---------|-----------|
|
|
1827
|
+
| `modelValue` | `any` | — | Value (v-model) |
|
|
1828
|
+
| `endpoint` | `{ apiUrl?, apiMethod? }` | — | Endpoint API |
|
|
1829
|
+
| `apiParams` | `object` | `{}` | Parameter tambahan |
|
|
1830
|
+
| `fetchItems` | `Function` | — | Custom fetch |
|
|
1831
|
+
| `itemTitle` | `string\|Function` | `'name'` | Field label |
|
|
1832
|
+
| `itemValue` | `string\|Function` | `'id'` | Field nilai |
|
|
1833
|
+
| `itemSubtitle` | `string\|Function` | — | Field subtitle item |
|
|
1834
|
+
| `returnObject` | `boolean` | `false` | Return objek penuh |
|
|
1835
|
+
| `autoFetchOnOpen` | `boolean` | `true` | Fetch saat buka |
|
|
1836
|
+
| `refreshOnOpen` | `boolean` | `false` | Refresh data setiap buka |
|
|
1837
|
+
| `clearable` | `boolean` | `false` | Tombol clear |
|
|
1838
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
1839
|
+
| `visible` | `boolean` | `true` | Tampilkan |
|
|
1840
|
+
| `skeleton` | `boolean` | `false` | Skeleton |
|
|
1841
|
+
| `error` | `string\|null` | `null` | Error message |
|
|
1842
|
+
| `icon` | `Component` | — | Ikon prefix |
|
|
1843
|
+
| `pageSize` | `number` | `20` | Jumlah item per halaman |
|
|
1844
|
+
|
|
1845
|
+
```vue
|
|
1846
|
+
<template>
|
|
1847
|
+
<DCodeInfiniteAutoComplete
|
|
1848
|
+
v-model="form.userId"
|
|
1849
|
+
label="Pilih User"
|
|
1850
|
+
:endpoint="{ apiUrl: '/api/users', apiMethod: 'GET' }"
|
|
1851
|
+
item-title="fullName"
|
|
1852
|
+
item-value="id"
|
|
1853
|
+
:page-size="15"
|
|
1854
|
+
/>
|
|
1855
|
+
</template>
|
|
1856
|
+
```
|
|
1857
|
+
|
|
1858
|
+
---
|
|
1859
|
+
|
|
1860
|
+
### `DCodeMultiSelect`
|
|
1861
|
+
|
|
1862
|
+
Dropdown multi-pilihan dengan chip tags dan search.
|
|
1863
|
+
|
|
1864
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1865
|
+
|------|------|---------|-----------|
|
|
1866
|
+
| `modelValue` | `any[]` | — | Array nilai terpilih (v-model) |
|
|
1867
|
+
| `items` | `object[]` | `[]` | Data statis |
|
|
1868
|
+
| `endpoint` | `{ apiUrl?, apiMethod?, apiOptions? }` | — | Endpoint API |
|
|
1869
|
+
| `fetchItems` | `Function` | — | Custom fetch |
|
|
1870
|
+
| `autoFetchOnOpen` | `boolean` | `true` | Fetch saat buka |
|
|
1871
|
+
| `autoFetchOnMount` | `boolean` | `false` | Fetch saat mount |
|
|
1872
|
+
| `refreshOnOpen` | `boolean` | `false` | Refresh setiap buka |
|
|
1873
|
+
| `label` | `string` | `''` | Label dropdown |
|
|
1874
|
+
| `placeholder` | `string` | `'Pilih data…'` | Placeholder |
|
|
1875
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
1876
|
+
| `clearable` | `boolean` | `false` | Tombol clear |
|
|
1877
|
+
| `searchable` | `boolean` | `false` | Aktifkan search |
|
|
1878
|
+
| `searchPlaceholder` | `string` | `'Cari…'` | Placeholder search |
|
|
1879
|
+
| `visible` | `boolean` | `true` | Tampilkan |
|
|
1880
|
+
| `skeleton` | `boolean` | `false` | Skeleton |
|
|
1881
|
+
| `maxChips` | `number\|null` | `2` | Maks chip ditampilkan |
|
|
1882
|
+
| `maxSelected` | `number\|null` | `3` | Maks pilihan |
|
|
1883
|
+
| `itemTitle` | `string\|Function` | — | Field label |
|
|
1884
|
+
| `itemValue` | `string\|Function` | — | Field nilai |
|
|
1885
|
+
| `returnObject` | `boolean` | — | Return objek penuh |
|
|
1886
|
+
| `error` | `string\|null` | — | Error message |
|
|
1887
|
+
|
|
1888
|
+
```vue
|
|
1889
|
+
<template>
|
|
1890
|
+
<DCodeMultiSelect
|
|
1891
|
+
v-model="form.tags"
|
|
1892
|
+
label="Tags"
|
|
1893
|
+
:items="availableTags"
|
|
1894
|
+
item-title="name"
|
|
1895
|
+
item-value="id"
|
|
1896
|
+
:max-selected="5"
|
|
1897
|
+
searchable
|
|
1898
|
+
/>
|
|
1899
|
+
</template>
|
|
1900
|
+
```
|
|
1901
|
+
|
|
1902
|
+
---
|
|
1903
|
+
|
|
1904
|
+
### `DCodeDatePicker`
|
|
1905
|
+
|
|
1906
|
+
Date picker atau date range picker dengan berbagai format.
|
|
1907
|
+
|
|
1908
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1909
|
+
|------|------|---------|-----------|
|
|
1910
|
+
| `modelValue` | `string\|[string,string]\|null` | — | Value tanggal (v-model) |
|
|
1911
|
+
| `range` | `boolean` | `true` | Mode range (pilih 2 tanggal) |
|
|
1912
|
+
| `autoRangeDays` | `number\|null` | `null` | Otomatis set range N hari |
|
|
1913
|
+
| `disablePast` | `boolean` | `false` | Nonaktifkan tanggal masa lalu |
|
|
1914
|
+
| `maxDaysAhead` | `number\|null` | `null` | Maks hari ke depan |
|
|
1915
|
+
| `placeholder` | `string` | `'Pilih tanggal'` | Placeholder |
|
|
1916
|
+
| `label` | `string` | — | Label |
|
|
1917
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
1918
|
+
| `clearable` | `boolean` | `true` | Tombol clear |
|
|
1919
|
+
| `displayFormat` | `string` | `'dd/MM/yyyy'` | Format tampilan |
|
|
1920
|
+
| `modelType` | `string` | `'yyyy-MM-dd'` | Format v-model |
|
|
1921
|
+
| `locale` | `'id'\|'en'` | `'en'` | Bahasa kalender |
|
|
1922
|
+
| `placement` | `string` | `'bottom-start'` | Posisi dropdown |
|
|
1923
|
+
| `prependInner` | `string` | — | Teks prefix |
|
|
1924
|
+
| `error` | `string\|null` | `null` | Error message |
|
|
1925
|
+
| `tone` | `string` | `'primary'` | Warna tema kalender |
|
|
1926
|
+
| `skeleton` | `boolean` | `false` | Skeleton loading |
|
|
1927
|
+
|
|
1928
|
+
```vue
|
|
1929
|
+
<template>
|
|
1930
|
+
<!-- Single date -->
|
|
1931
|
+
<DCodeDatePicker
|
|
1932
|
+
v-model="form.tanggal"
|
|
1933
|
+
label="Tanggal Lahir"
|
|
1934
|
+
:range="false"
|
|
1935
|
+
display-format="dd MMMM yyyy"
|
|
1936
|
+
locale="id"
|
|
1937
|
+
/>
|
|
1938
|
+
|
|
1939
|
+
<!-- Date range -->
|
|
1940
|
+
<DCodeDatePicker
|
|
1941
|
+
v-model="form.periode"
|
|
1942
|
+
label="Periode"
|
|
1943
|
+
:range="true"
|
|
1944
|
+
/>
|
|
1945
|
+
</template>
|
|
1946
|
+
```
|
|
1947
|
+
|
|
1948
|
+
---
|
|
1949
|
+
|
|
1950
|
+
### `DCodeCurrencyField`
|
|
1951
|
+
|
|
1952
|
+
Input angka dengan format mata uang otomatis.
|
|
1953
|
+
|
|
1954
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1955
|
+
|------|------|---------|-----------|
|
|
1956
|
+
| `modelValue` | `number\|null` | — | Nilai angka (v-model) |
|
|
1957
|
+
| `currency` | `string` | `'IDR'` | Kode mata uang (`'IDR'`, `'USD'`) |
|
|
1958
|
+
| `locale` | `string` | `'en-US'` | Locale format angka |
|
|
1959
|
+
| `label` | `string` | — | Label field |
|
|
1960
|
+
| `placeholder` | `string` | — | Placeholder |
|
|
1961
|
+
| `error` | `string\|null` | — | Error message |
|
|
1962
|
+
| `disabled` | `boolean` | — | Nonaktifkan |
|
|
1963
|
+
| `readonly` | `boolean` | — | Read-only |
|
|
1964
|
+
| `clearable` | `boolean` | — | Tombol clear |
|
|
1965
|
+
| `hideClearWhenEmpty` | `boolean` | `true` | Sembunyikan clear jika kosong |
|
|
1966
|
+
| `prependInner` | `string` | — | Override simbol mata uang |
|
|
1967
|
+
| `skeleton` | `boolean` | `false` | Skeleton loading |
|
|
1968
|
+
|
|
1969
|
+
```vue
|
|
1970
|
+
<template>
|
|
1971
|
+
<DCodeCurrencyField
|
|
1972
|
+
v-model="form.harga"
|
|
1973
|
+
label="Harga"
|
|
1974
|
+
currency="IDR"
|
|
1975
|
+
clearable
|
|
1976
|
+
:error="errors.harga"
|
|
1977
|
+
/>
|
|
1978
|
+
</template>
|
|
1979
|
+
```
|
|
1980
|
+
|
|
1981
|
+
---
|
|
1982
|
+
|
|
1983
|
+
### `DCodeCheckbox`
|
|
1984
|
+
|
|
1985
|
+
Checkbox dengan label dan dukungan indeterminate state.
|
|
1986
|
+
|
|
1987
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
1988
|
+
|------|------|---------|-----------|
|
|
1989
|
+
| `modelValue` | `boolean\|null` | — | Nilai checkbox (v-model) |
|
|
1990
|
+
| `visible` | `boolean` | `true` | Tampilkan |
|
|
1991
|
+
| `skeleton` | `boolean` | `false` | Skeleton |
|
|
1992
|
+
| `indeterminate` | `boolean` | `false` | State indeterminate |
|
|
1993
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
1994
|
+
|
|
1995
|
+
Gunakan attr `label` untuk teks di samping checkbox.
|
|
1996
|
+
|
|
1997
|
+
```vue
|
|
1998
|
+
<template>
|
|
1999
|
+
<DCodeCheckbox
|
|
2000
|
+
v-model="form.setuju"
|
|
2001
|
+
label="Saya setuju dengan syarat & ketentuan"
|
|
2002
|
+
required
|
|
2003
|
+
/>
|
|
2004
|
+
</template>
|
|
2005
|
+
```
|
|
2006
|
+
|
|
2007
|
+
---
|
|
2008
|
+
|
|
2009
|
+
### `DCodeSwitch`
|
|
2010
|
+
|
|
2011
|
+
Toggle switch dengan berbagai ukuran, warna, dan tooltip.
|
|
2012
|
+
|
|
2013
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2014
|
+
|------|------|---------|-----------|
|
|
2015
|
+
| `modelValue` | `boolean` | `false` | Status switch (v-model) |
|
|
2016
|
+
| `label` | `string` | `''` | Label teks |
|
|
2017
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2018
|
+
| `size` | `'small'\|'medium'\|'large'` | `'medium'` | Ukuran switch |
|
|
2019
|
+
| `color` | `'primary'\|'success'\|'warning'\|'danger'` | `'primary'` | Warna aktif |
|
|
2020
|
+
| `loading` | `boolean` | `false` | Status loading |
|
|
2021
|
+
| `labelPosition` | `'left'\|'right'` | `'right'` | Posisi label |
|
|
2022
|
+
| `required` | `boolean` | `false` | Required |
|
|
2023
|
+
| `showTooltip` | `boolean` | `false` | Tampilkan tooltip |
|
|
2024
|
+
| `tooltipActiveText` | `string` | `'Aktif'` | Teks tooltip saat aktif |
|
|
2025
|
+
| `tooltipInactiveText` | `string` | `'Tidak Aktif'` | Teks tooltip saat nonaktif |
|
|
2026
|
+
|
|
2027
|
+
```vue
|
|
2028
|
+
<template>
|
|
2029
|
+
<DCodeSwitch
|
|
2030
|
+
v-model="form.isActive"
|
|
2031
|
+
label="Status Aktif"
|
|
2032
|
+
color="success"
|
|
2033
|
+
size="medium"
|
|
2034
|
+
:show-tooltip="true"
|
|
2035
|
+
/>
|
|
2036
|
+
</template>
|
|
2037
|
+
```
|
|
2038
|
+
|
|
2039
|
+
---
|
|
2040
|
+
|
|
2041
|
+
### `DCodeRadio`
|
|
2042
|
+
|
|
2043
|
+
Grup radio button yang bisa fetch opsi dari API.
|
|
2044
|
+
|
|
2045
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2046
|
+
|------|------|---------|-----------|
|
|
2047
|
+
| `modelValue` | `any` | — | Value terpilih (v-model) |
|
|
2048
|
+
| `label` | `string` | `'name'` | Key field label dari opsi |
|
|
2049
|
+
| `options` | `any[]` | `[]` | Opsi data statis |
|
|
2050
|
+
| `apiUrl` | `string` | `''` | URL endpoint API untuk fetch opsi |
|
|
2051
|
+
| `apiMethod` | `'GET'\|'POST'` | `'GET'` | HTTP method |
|
|
2052
|
+
| `dataPath` | `string` | `''` | Path ke array data dalam response |
|
|
2053
|
+
| `layout` | `boolean` | `true` | Layout horizontal (true) / vertikal |
|
|
2054
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2055
|
+
| `required` | `boolean` | `false` | Required |
|
|
2056
|
+
| `error` | `string\|null` | `null` | Error message |
|
|
2057
|
+
| `size` | `'sm'\|'md'\|'lg'` | `'md'` | Ukuran radio |
|
|
2058
|
+
| `color` | `string` | `'blue'` | Warna accent |
|
|
2059
|
+
| `model` | `string` | `'id'` | Key field nilai dari opsi |
|
|
2060
|
+
|
|
2061
|
+
```vue
|
|
2062
|
+
<template>
|
|
2063
|
+
<!-- Dengan opsi statis -->
|
|
2064
|
+
<DCodeRadio
|
|
2065
|
+
v-model="form.gender"
|
|
2066
|
+
:options="[
|
|
2067
|
+
{ id: 'L', name: 'Laki-laki' },
|
|
2068
|
+
{ id: 'P', name: 'Perempuan' },
|
|
2069
|
+
]"
|
|
2070
|
+
/>
|
|
2071
|
+
|
|
2072
|
+
<!-- Dengan API -->
|
|
2073
|
+
<DCodeRadio
|
|
2074
|
+
v-model="form.statusId"
|
|
2075
|
+
api-url="/api/master/status"
|
|
2076
|
+
label="nama"
|
|
2077
|
+
model="id"
|
|
2078
|
+
/>
|
|
2079
|
+
</template>
|
|
2080
|
+
```
|
|
2081
|
+
|
|
2082
|
+
---
|
|
2083
|
+
|
|
2084
|
+
### `DCodeRadioCustom`
|
|
2085
|
+
|
|
2086
|
+
Radio button dengan tampilan card kustom dan ikon.
|
|
2087
|
+
|
|
2088
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2089
|
+
|------|------|---------|-----------|
|
|
2090
|
+
| `modelValue` | `any` | — | Value (v-model) |
|
|
2091
|
+
| `icon` | `string` | — | Nama ikon Lucide untuk setiap card |
|
|
2092
|
+
| `label` | `string` | `'name'` | Key field label |
|
|
2093
|
+
| `options` | `any[]` | `[]` | Opsi statis |
|
|
2094
|
+
| `apiUrl` | `string` | `''` | URL API |
|
|
2095
|
+
| `apiMethod` | `'GET'\|'POST'` | `'GET'` | HTTP method |
|
|
2096
|
+
| `dataPath` | `string` | `''` | Path data dalam response |
|
|
2097
|
+
| `layout` | `boolean` | `true` | Horizontal layout |
|
|
2098
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2099
|
+
| `required` | `boolean` | `false` | Required |
|
|
2100
|
+
| `error` | `string\|null` | `null` | Error message |
|
|
2101
|
+
| `size` | `'sm'\|'md'\|'lg'` | `'md'` | Ukuran |
|
|
2102
|
+
| `color` | `string` | `'blue'` | Warna highlight |
|
|
2103
|
+
| `model` | `string` | `'id'` | Key field nilai |
|
|
2104
|
+
|
|
2105
|
+
```vue
|
|
2106
|
+
<template>
|
|
2107
|
+
<DCodeRadioCustom
|
|
2108
|
+
v-model="form.plan"
|
|
2109
|
+
:options="[
|
|
2110
|
+
{ id: 'basic', name: 'Basic', icon: 'Star' },
|
|
2111
|
+
{ id: 'pro', name: 'Pro', icon: 'Zap' },
|
|
2112
|
+
]"
|
|
2113
|
+
label="name"
|
|
2114
|
+
model="id"
|
|
2115
|
+
/>
|
|
2116
|
+
</template>
|
|
2117
|
+
```
|
|
2118
|
+
|
|
2119
|
+
---
|
|
2120
|
+
|
|
2121
|
+
### `DCodeOtpInput`
|
|
2122
|
+
|
|
2123
|
+
Input OTP digit-per-digit dengan submit otomatis ke API.
|
|
2124
|
+
|
|
2125
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2126
|
+
|------|------|---------|-----------|
|
|
2127
|
+
| `modelValue` | `string` | `''` | Nilai OTP (v-model) |
|
|
2128
|
+
| `totalInput` | `number` | `6` | Jumlah digit OTP |
|
|
2129
|
+
| `default` | `string` | `''` | Default nilai awal |
|
|
2130
|
+
| `label` | `string` | `''` | Label |
|
|
2131
|
+
| `endpoint` | `{ apiMethod?, apiUrl? }` | — | Endpoint untuk submit OTP |
|
|
2132
|
+
| `submitFn` | `Function` | — | Custom submit function |
|
|
2133
|
+
|
|
2134
|
+
Events: `complete` (OTP penuh), `success`, `error`, `loading`
|
|
2135
|
+
|
|
2136
|
+
```vue
|
|
2137
|
+
<template>
|
|
2138
|
+
<DCodeOtpInput
|
|
2139
|
+
v-model="otp"
|
|
2140
|
+
:total-input="6"
|
|
2141
|
+
label="Kode OTP"
|
|
2142
|
+
:endpoint="{ apiUrl: '/api/auth/verify-otp', apiMethod: 'POST' }"
|
|
2143
|
+
@complete="handleOtpComplete"
|
|
2144
|
+
/>
|
|
2145
|
+
</template>
|
|
2146
|
+
```
|
|
2147
|
+
|
|
2148
|
+
---
|
|
2149
|
+
|
|
2150
|
+
### `DCodeFileField`
|
|
2151
|
+
|
|
2152
|
+
Upload file dengan validasi tipe & ukuran, preview, dan upload ke API.
|
|
2153
|
+
|
|
2154
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2155
|
+
|------|------|---------|-----------|
|
|
2156
|
+
| `modelValue` | `File\|null` | — | File terpilih (v-model) |
|
|
2157
|
+
| `label` | `string` | `'Upload File'` | Label |
|
|
2158
|
+
| `hint` | `string` | — | Teks petunjuk |
|
|
2159
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2160
|
+
| `previewHeight` | `number` | `190` | Tinggi preview (px) |
|
|
2161
|
+
| `previewWidth` | `number` | — | Lebar preview (px) |
|
|
2162
|
+
| `allowedTypes` | `string\|null` | `null` | Tipe file diizinkan (e.g. `'pdf,doc'`) |
|
|
2163
|
+
| `accept` | `string` | — | Attribute accept HTML |
|
|
2164
|
+
| `minSizeMb` | `number\|null` | `null` | Ukuran file minimum (MB) |
|
|
2165
|
+
| `maxSizeMb` | `number` | `5` | Ukuran file maksimum (MB) |
|
|
2166
|
+
| `endpoint` | `{ apiMethod?, apiUrl? }` | — | Endpoint upload |
|
|
2167
|
+
| `uploadFn` | `Function` | — | Custom upload function |
|
|
2168
|
+
|
|
2169
|
+
```vue
|
|
2170
|
+
<template>
|
|
2171
|
+
<DCodeFileField
|
|
2172
|
+
v-model="form.dokumen"
|
|
2173
|
+
label="Upload Dokumen"
|
|
2174
|
+
allowed-types="pdf,doc,docx"
|
|
2175
|
+
:max-size-mb="10"
|
|
2176
|
+
:endpoint="{ apiUrl: '/api/upload', apiMethod: 'POST' }"
|
|
2177
|
+
@uploaded="handleUploaded"
|
|
2178
|
+
@error="handleError"
|
|
2179
|
+
/>
|
|
2180
|
+
</template>
|
|
2181
|
+
```
|
|
2182
|
+
|
|
2183
|
+
---
|
|
2184
|
+
|
|
2185
|
+
### `DCodeImageField`
|
|
2186
|
+
|
|
2187
|
+
Upload gambar dengan crop/resize dialog terintegrasi.
|
|
2188
|
+
|
|
2189
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2190
|
+
|------|------|---------|-----------|
|
|
2191
|
+
| `modelValue` | `File\|null` | — | File gambar (v-model) |
|
|
2192
|
+
| `label` | `string` | `'Upload Gambar'` | Label |
|
|
2193
|
+
| `hint` | `string` | `'Drag & drop atau klik area ini'` | Petunjuk |
|
|
2194
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2195
|
+
| `allowedTypes` | `string\|null` | `null` | Tipe gambar diizinkan |
|
|
2196
|
+
| `accept` | `string` | `'image/*'` | Accept HTML |
|
|
2197
|
+
| `minSizeMb` | `number\|null` | `null` | Min ukuran (MB) |
|
|
2198
|
+
| `maxSizeMb` | `number` | `2` | Maks ukuran (MB) |
|
|
2199
|
+
| `title` | `string` | `'Hasil Gambar'` | Judul preview |
|
|
2200
|
+
| `description` | `string` | `''` | Deskripsi preview |
|
|
2201
|
+
| `previewWidth` | `number` | `360` | Lebar preview |
|
|
2202
|
+
| `previewHeight` | `number` | `220` | Tinggi preview |
|
|
2203
|
+
| `aspectRatio` | `number\|null` | `null` | Rasio aspek crop |
|
|
2204
|
+
| `outputWidth` | `number\|null` | `null` | Lebar output (px) |
|
|
2205
|
+
| `outputHeight` | `number\|null` | `null` | Tinggi output (px) |
|
|
2206
|
+
| `quality` | `number` | `0.92` | Kualitas output (0-1) |
|
|
2207
|
+
| `autoOpenEditor` | `boolean` | `true` | Buka editor saat file dipilih |
|
|
2208
|
+
| `minWidth` | `number\|null` | `null` | Min lebar gambar |
|
|
2209
|
+
| `minHeight` | `number\|null` | `null` | Min tinggi gambar |
|
|
2210
|
+
| `maxWidth` | `number\|null` | `null` | Maks lebar gambar |
|
|
2211
|
+
| `maxHeight` | `number\|null` | `null` | Maks tinggi gambar |
|
|
2212
|
+
| `endpoint` | `{ apiMethod?, apiUrl? }` | — | Endpoint upload |
|
|
2213
|
+
| `uploadFn` | `Function` | — | Custom upload |
|
|
2214
|
+
| `editUseOriginal` | `boolean` | `false` | Edit dari foto asli (bukan ter-crop) |
|
|
2215
|
+
|
|
2216
|
+
```vue
|
|
2217
|
+
<template>
|
|
2218
|
+
<DCodeImageField
|
|
2219
|
+
v-model="form.foto"
|
|
2220
|
+
label="Foto Profil"
|
|
2221
|
+
:aspect-ratio="1"
|
|
2222
|
+
:output-width="300"
|
|
2223
|
+
:output-height="300"
|
|
2224
|
+
:max-size-mb="2"
|
|
2225
|
+
/>
|
|
2226
|
+
</template>
|
|
2227
|
+
```
|
|
2228
|
+
|
|
2229
|
+
---
|
|
2230
|
+
|
|
2231
|
+
### `DCodeDropzone`
|
|
2232
|
+
|
|
2233
|
+
Area drag-and-drop untuk upload file.
|
|
2234
|
+
|
|
2235
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2236
|
+
|------|------|---------|-----------|
|
|
2237
|
+
| `label` | `string` | `'Upload File'` | Label |
|
|
2238
|
+
| `hint` | `string` | `'Drag & drop file di sini atau klik area ini'` | Petunjuk |
|
|
2239
|
+
| `accept` | `string` | `'image/*'` | Tipe file diterima |
|
|
2240
|
+
| `maxSizeMb` | `number` | `2` | Maks ukuran (MB) |
|
|
2241
|
+
| `mode` | `'single'\|'multiple'` | `'single'` | Mode pilih satu/banyak |
|
|
2242
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2243
|
+
| `showSelected` | `boolean` | `true` | Tampilkan nama file terpilih |
|
|
2244
|
+
| `error` | `string\|null` | `null` | Error message |
|
|
2245
|
+
|
|
2246
|
+
Events: `picked` (File), `picked:multiple` (File[]), `error` (string)
|
|
2247
|
+
|
|
2248
|
+
```vue
|
|
2249
|
+
<template>
|
|
2250
|
+
<DCodeDropzone
|
|
2251
|
+
label="Upload CSV"
|
|
2252
|
+
accept=".csv,.xlsx"
|
|
2253
|
+
:max-size-mb="5"
|
|
2254
|
+
mode="single"
|
|
2255
|
+
@picked="handleFile"
|
|
2256
|
+
/>
|
|
2257
|
+
</template>
|
|
2258
|
+
```
|
|
2259
|
+
|
|
2260
|
+
---
|
|
2261
|
+
|
|
2262
|
+
### `DCodeUploadFile`
|
|
2263
|
+
|
|
2264
|
+
Komponen upload file sederhana dengan tombol browse.
|
|
2265
|
+
|
|
2266
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2267
|
+
|------|------|---------|-----------|
|
|
2268
|
+
| `modelValue` | `any` | — | File (v-model) |
|
|
2269
|
+
| `label` | `string` | — | Label |
|
|
2270
|
+
| `accept` | `string` | — | Tipe file diterima |
|
|
2271
|
+
| `errorMessage` | `string` | — | Pesan error |
|
|
2272
|
+
|
|
2273
|
+
```vue
|
|
2274
|
+
<template>
|
|
2275
|
+
<DCodeUploadFile
|
|
2276
|
+
v-model="form.file"
|
|
2277
|
+
label="Upload Berkas"
|
|
2278
|
+
accept=".pdf,.doc"
|
|
2279
|
+
/>
|
|
2280
|
+
</template>
|
|
2281
|
+
```
|
|
2282
|
+
|
|
2283
|
+
---
|
|
2284
|
+
|
|
2285
|
+
### `DCodeFileResult`
|
|
2286
|
+
|
|
2287
|
+
Menampilkan preview file yang sudah dipilih dengan opsi edit/replace/remove.
|
|
2288
|
+
|
|
2289
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2290
|
+
|------|------|---------|-----------|
|
|
2291
|
+
| `file` | `File\|null` | — | File untuk ditampilkan |
|
|
2292
|
+
| `title` | `string` | `'File Terpilih'` | Judul card |
|
|
2293
|
+
| `description` | `string` | `''` | Deskripsi |
|
|
2294
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan aksi |
|
|
2295
|
+
| `previewHeight` | `number` | `190` | Tinggi preview |
|
|
2296
|
+
| `previewWidth` | `number` | `220` | Lebar preview |
|
|
2297
|
+
| `showEdit` | `boolean` | `true` | Tampilkan tombol Edit |
|
|
2298
|
+
| `showReplace` | `boolean` | `true` | Tampilkan tombol Ganti |
|
|
2299
|
+
| `showRemove` | `boolean` | `true` | Tampilkan tombol Hapus |
|
|
2300
|
+
| `forceFileIcon` | `boolean` | `false` | Selalu tampilkan ikon file (bukan preview gambar) |
|
|
2301
|
+
|
|
2302
|
+
Events: `edit`, `replace`, `remove`
|
|
2303
|
+
|
|
2304
|
+
```vue
|
|
2305
|
+
<template>
|
|
2306
|
+
<DCodeFileResult
|
|
2307
|
+
:file="uploadedFile"
|
|
2308
|
+
title="Dokumen Saya"
|
|
2309
|
+
:show-edit="false"
|
|
2310
|
+
@replace="handleReplace"
|
|
2311
|
+
@remove="handleRemove"
|
|
2312
|
+
/>
|
|
2313
|
+
</template>
|
|
2314
|
+
```
|
|
2315
|
+
|
|
2316
|
+
---
|
|
2317
|
+
|
|
2318
|
+
### `DCodeImageResult`
|
|
2319
|
+
|
|
2320
|
+
Menampilkan preview gambar yang sudah dipilih.
|
|
2321
|
+
|
|
2322
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2323
|
+
|------|------|---------|-----------|
|
|
2324
|
+
| `file` | `File\|null` | — | File gambar |
|
|
2325
|
+
| `title` | `string` | `'Hasil Gambar'` | Judul card |
|
|
2326
|
+
| `description` | `string` | `''` | Deskripsi |
|
|
2327
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2328
|
+
| `previewHeight` | `number` | `190` | Tinggi preview |
|
|
2329
|
+
| `previewWidth` | `number` | `220` | Lebar preview |
|
|
2330
|
+
| `showEdit` | `boolean` | `true` | Tombol edit |
|
|
2331
|
+
| `showReplace` | `boolean` | `true` | Tombol ganti |
|
|
2332
|
+
| `showRemove` | `boolean` | `true` | Tombol hapus |
|
|
2333
|
+
|
|
2334
|
+
Events: `edit`, `replace`, `remove`
|
|
2335
|
+
|
|
2336
|
+
```vue
|
|
2337
|
+
<template>
|
|
2338
|
+
<DCodeImageResult
|
|
2339
|
+
:file="imageFile"
|
|
2340
|
+
title="Foto Profil"
|
|
2341
|
+
@edit="openCropper"
|
|
2342
|
+
@remove="clearImage"
|
|
2343
|
+
/>
|
|
2344
|
+
</template>
|
|
2345
|
+
```
|
|
2346
|
+
|
|
2347
|
+
---
|
|
2348
|
+
|
|
2349
|
+
### `DCodeImageCropperDialog`
|
|
2350
|
+
|
|
2351
|
+
Dialog crop / resize gambar menggunakan Uppy + ImageEditor.
|
|
2352
|
+
|
|
2353
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2354
|
+
|------|------|---------|-----------|
|
|
2355
|
+
| `modelValue` | `boolean` | — | Status dialog (v-model) |
|
|
2356
|
+
| `file` | `File\|null` | — | File gambar sumber |
|
|
2357
|
+
| `title` | `string` | `'Resize & Crop Gambar'` | Judul dialog |
|
|
2358
|
+
| `description` | `string` | `''` | Deskripsi |
|
|
2359
|
+
| `aspectRatio` | `number\|null` | `null` | Rasio aspek (e.g. `16/9`) |
|
|
2360
|
+
| `outputWidth` | `number\|null` | `null` | Lebar output (px) |
|
|
2361
|
+
| `outputHeight` | `number\|null` | `null` | Tinggi output (px) |
|
|
2362
|
+
| `quality` | `number` | `0.92` | Kualitas gambar output |
|
|
2363
|
+
| `autoOpenEditor` | `boolean` | `false` | Buka editor otomatis |
|
|
2364
|
+
|
|
2365
|
+
Events: `update:modelValue`, `done` (File), `cancel`
|
|
2366
|
+
|
|
2367
|
+
```vue
|
|
2368
|
+
<template>
|
|
2369
|
+
<DCodeImageCropperDialog
|
|
2370
|
+
v-model="cropperOpen"
|
|
2371
|
+
:file="selectedFile"
|
|
2372
|
+
:aspect-ratio="16/9"
|
|
2373
|
+
:output-width="1280"
|
|
2374
|
+
:output-height="720"
|
|
2375
|
+
@done="handleCropDone"
|
|
2376
|
+
/>
|
|
2377
|
+
</template>
|
|
2378
|
+
```
|
|
2379
|
+
|
|
2380
|
+
---
|
|
2381
|
+
|
|
2382
|
+
### `DCodeDialog`
|
|
2383
|
+
|
|
2384
|
+
Modal dialog yang dapat dikonfigurasi dengan slot.
|
|
2385
|
+
|
|
2386
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2387
|
+
|------|------|---------|-----------|
|
|
2388
|
+
| `modelValue` | `boolean` | `false` | Status buka/tutup (v-model) |
|
|
2389
|
+
| `title` | `string` | `''` | Judul dialog |
|
|
2390
|
+
| `icon` | `string` | `''` | Nama ikon Lucide di header |
|
|
2391
|
+
| `maxWidth` | `string\|number` | `500` | Lebar maksimum (px atau string CSS) |
|
|
2392
|
+
| `persistent` | `boolean` | `true` | Tidak tutup saat klik di luar |
|
|
2393
|
+
| `contentClass` | `string` | `'p-5'` | Class pada area konten |
|
|
2394
|
+
| `contentStyle` | `object\|string` | `{}` | Style tambahan konten |
|
|
2395
|
+
|
|
2396
|
+
```vue
|
|
2397
|
+
<template>
|
|
2398
|
+
<DCodeDialog v-model="dialogOpen" title="Konfirmasi Hapus" icon="Trash2" :max-width="400">
|
|
2399
|
+
<p>Apakah Anda yakin ingin menghapus data ini?</p>
|
|
2400
|
+
<template #footer>
|
|
2401
|
+
<DCodeButton text="Batal" variant="outline" @click="dialogOpen = false" />
|
|
2402
|
+
<DCodeButton text="Hapus" variant="destructive" @click="confirmDelete" />
|
|
2403
|
+
</template>
|
|
2404
|
+
</DCodeDialog>
|
|
2405
|
+
</template>
|
|
2406
|
+
```
|
|
2407
|
+
|
|
2408
|
+
---
|
|
2409
|
+
|
|
2410
|
+
### `DCodeDialogCloseBtn`
|
|
2411
|
+
|
|
2412
|
+
Tombol close berbentuk lingkaran untuk digunakan di dalam dialog.
|
|
2413
|
+
|
|
2414
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2415
|
+
|------|------|---------|-----------|
|
|
2416
|
+
| `icon` | `string` | `'X'` | Nama ikon Lucide |
|
|
2417
|
+
| `iconSize` | `string\|number` | `22` | Ukuran ikon |
|
|
2418
|
+
|
|
2419
|
+
```vue
|
|
2420
|
+
<template>
|
|
2421
|
+
<DCodeDialogCloseBtn @click="dialogOpen = false" />
|
|
2422
|
+
</template>
|
|
2423
|
+
```
|
|
2424
|
+
|
|
2425
|
+
---
|
|
2426
|
+
|
|
2427
|
+
### `DCodeIconDropdown`
|
|
2428
|
+
|
|
2429
|
+
Dropdown untuk memilih ikon dari library Lucide.
|
|
2430
|
+
|
|
2431
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2432
|
+
|------|------|---------|-----------|
|
|
2433
|
+
| `modelValue` | `string\|null` | — | Nama ikon terpilih (v-model) |
|
|
2434
|
+
| `label` | `string` | — | Label field |
|
|
2435
|
+
| `placeholder` | `string` | `'Pilih icon…'` | Placeholder |
|
|
2436
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2437
|
+
| `required` | `boolean` | `false` | Required |
|
|
2438
|
+
| `clearable` | `boolean` | `true` | Tombol clear |
|
|
2439
|
+
| `columns` | `number` | `4` | Jumlah kolom grid ikon |
|
|
2440
|
+
| `maxHeight` | `string` | `'335px'` | Tinggi maksimum dropdown |
|
|
2441
|
+
| `iconSize` | `number` | `20` | Ukuran ikon dalam grid (px) |
|
|
2442
|
+
| `pageSize` | `number` | `120` | Jumlah ikon per halaman |
|
|
2443
|
+
|
|
2444
|
+
```vue
|
|
2445
|
+
<template>
|
|
2446
|
+
<DCodeIconDropdown
|
|
2447
|
+
v-model="form.icon"
|
|
2448
|
+
label="Pilih Ikon Menu"
|
|
2449
|
+
:columns="5"
|
|
2450
|
+
clearable
|
|
2451
|
+
/>
|
|
2452
|
+
</template>
|
|
2453
|
+
```
|
|
2454
|
+
|
|
2455
|
+
---
|
|
2456
|
+
|
|
2457
|
+
### `DCodeLabel`
|
|
2458
|
+
|
|
2459
|
+
Label teks dengan ikon opsional dan berbagai ukuran.
|
|
2460
|
+
|
|
2461
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2462
|
+
|------|------|---------|-----------|
|
|
2463
|
+
| `label` | `string` | — | Teks label (alias dari `text`) |
|
|
2464
|
+
| `text` | `string` | — | Teks label |
|
|
2465
|
+
| `textSize` | `'xs'\|'sm'\|'base'\|'lg'\|'xl'\|'2xl'\|'3xl'\|'4xl'\|'5xl'\|'6xl'` | `'xs'` | Ukuran teks |
|
|
2466
|
+
| `textType` | `'BOLD'\|'ITALIC'\|'REGULAR'` | `'REGULAR'` | Gaya teks |
|
|
2467
|
+
| `icon` | `string` | — | Nama ikon Lucide |
|
|
2468
|
+
| `iconClass` | `string` | — | Class tambahan untuk ikon |
|
|
2469
|
+
|
|
2470
|
+
```vue
|
|
2471
|
+
<template>
|
|
2472
|
+
<DCodeLabel label="Total Transaksi" text-size="lg" text-type="BOLD" />
|
|
2473
|
+
<DCodeLabel text="Info" icon="Info" text-size="sm" />
|
|
2474
|
+
</template>
|
|
2475
|
+
```
|
|
2476
|
+
|
|
2477
|
+
---
|
|
2478
|
+
|
|
2479
|
+
### `DCodeCard`
|
|
2480
|
+
|
|
2481
|
+
Kontainer kartu dengan header, subtitle, actions slot, dan footer slot.
|
|
2482
|
+
|
|
2483
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2484
|
+
|------|------|---------|-----------|
|
|
2485
|
+
| `level` | `1\|2\|3` | `1` | Level nesting (mempengaruhi background) |
|
|
2486
|
+
| `title` | `string` | — | Judul card |
|
|
2487
|
+
| `subtitle` | `string` | — | Subtitle card |
|
|
2488
|
+
| `shadow` | `boolean` | `false` | Tambahkan drop shadow |
|
|
2489
|
+
| `border` | `boolean` | `true` | Tampilkan border |
|
|
2490
|
+
| `padding` | `string` | `'p-6'` | Class padding (tailwind) |
|
|
2491
|
+
|
|
2492
|
+
Slots: `default` (konten), `header`, `actions`, `footer`
|
|
2493
|
+
|
|
2494
|
+
```vue
|
|
2495
|
+
<template>
|
|
2496
|
+
<DCodeCard title="Data Pengguna" subtitle="Daftar semua user" shadow>
|
|
2497
|
+
<template #actions>
|
|
2498
|
+
<DCodeButton text="Tambah" icon="Plus" />
|
|
2499
|
+
</template>
|
|
2500
|
+
|
|
2501
|
+
<!-- Konten -->
|
|
2502
|
+
<p>Isi card di sini...</p>
|
|
2503
|
+
|
|
2504
|
+
<template #footer>
|
|
2505
|
+
<span class="text-sm text-gray-500">Updated 5 minutes ago</span>
|
|
2506
|
+
</template>
|
|
2507
|
+
</DCodeCard>
|
|
2508
|
+
</template>
|
|
2509
|
+
```
|
|
2510
|
+
|
|
2511
|
+
---
|
|
2512
|
+
|
|
2513
|
+
### `DCodeProgressBar`
|
|
2514
|
+
|
|
2515
|
+
Progress bar global yang biasanya dipasang di layout untuk indikator loading API.
|
|
2516
|
+
|
|
2517
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2518
|
+
|------|------|---------|-----------|
|
|
2519
|
+
| `isApiLoading` | `boolean` | `false` | Tampilkan progress bar |
|
|
2520
|
+
| `apiProgress` | `number` | `0` | Persentase progress (0-100) |
|
|
2521
|
+
|
|
2522
|
+
```vue
|
|
2523
|
+
<template>
|
|
2524
|
+
<!-- Biasanya di App.vue atau Layout -->
|
|
2525
|
+
<DCodeProgressBar :is-api-loading="isLoading" :api-progress="loadingProgress" />
|
|
2526
|
+
</template>
|
|
2527
|
+
```
|
|
2528
|
+
|
|
2529
|
+
---
|
|
2530
|
+
|
|
2531
|
+
### `DCodeProgress`
|
|
2532
|
+
|
|
2533
|
+
Progress bar inline yang bisa digunakan di dalam konten.
|
|
2534
|
+
|
|
2535
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2536
|
+
|------|------|---------|-----------|
|
|
2537
|
+
| `modelValue` | `number` | `0` | Nilai progress (v-model) |
|
|
2538
|
+
| `max` | `number` | `100` | Nilai maksimum |
|
|
2539
|
+
| `label` | `string` | `''` | Label teks |
|
|
2540
|
+
| `showValue` | `boolean` | `false` | Tampilkan persentase |
|
|
2541
|
+
| `color` | `string` | `'blue'` | Warna bar (`'blue'`, `'green'`, `'red'`, `'yellow'`, `'purple'`, `'gray'`) |
|
|
2542
|
+
| `size` | `'sm'\|'md'\|'lg'` | `'md'` | Ketebalan bar |
|
|
2543
|
+
| `striped` | `boolean` | `false` | Tampilan garis-garis |
|
|
2544
|
+
| `animated` | `boolean` | `false` | Animasi gerak |
|
|
2545
|
+
|
|
2546
|
+
```vue
|
|
2547
|
+
<template>
|
|
2548
|
+
<DCodeProgress
|
|
2549
|
+
:model-value="75"
|
|
2550
|
+
label="Progress Upload"
|
|
2551
|
+
color="green"
|
|
2552
|
+
size="lg"
|
|
2553
|
+
:show-value="true"
|
|
2554
|
+
animated
|
|
2555
|
+
/>
|
|
2556
|
+
</template>
|
|
2557
|
+
```
|
|
2558
|
+
|
|
2559
|
+
---
|
|
2560
|
+
|
|
2561
|
+
### `DCodeSlider`
|
|
2562
|
+
|
|
2563
|
+
Input slider (range) dengan label, min/max, dan mark.
|
|
2564
|
+
|
|
2565
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2566
|
+
|------|------|---------|-----------|
|
|
2567
|
+
| `modelValue` | `number` | `0` | Nilai slider (v-model) |
|
|
2568
|
+
| `min` | `number` | `0` | Nilai minimum |
|
|
2569
|
+
| `max` | `number` | `100` | Nilai maksimum |
|
|
2570
|
+
| `step` | `number` | `1` | Langkah perubahan |
|
|
2571
|
+
| `disabled` | `boolean` | `false` | Nonaktifkan |
|
|
2572
|
+
| `label` | `string` | `''` | Label |
|
|
2573
|
+
| `required` | `boolean` | `false` | Required |
|
|
2574
|
+
| `error` | `string\|null` | `null` | Error message |
|
|
2575
|
+
| `showValue` | `boolean` | `true` | Tampilkan nilai saat ini |
|
|
2576
|
+
| `valuePrefix` | `string` | `''` | Prefix nilai (e.g. `'Rp'`) |
|
|
2577
|
+
| `valueSuffix` | `string` | `''` | Suffix nilai (e.g. `'%'`) |
|
|
2578
|
+
| `marks` | `{ value: number; label: string }[]` | `[]` | Mark pada slider |
|
|
2579
|
+
| `color` | `string` | `'blue'` | Warna slider |
|
|
2580
|
+
| `size` | `'sm'\|'md'\|'lg'` | `'md'` | Ukuran track |
|
|
2581
|
+
|
|
2582
|
+
```vue
|
|
2583
|
+
<template>
|
|
2584
|
+
<DCodeSlider
|
|
2585
|
+
v-model="form.diskon"
|
|
2586
|
+
label="Diskon"
|
|
2587
|
+
:min="0"
|
|
2588
|
+
:max="100"
|
|
2589
|
+
:step="5"
|
|
2590
|
+
value-suffix="%"
|
|
2591
|
+
color="green"
|
|
2592
|
+
:marks="[
|
|
2593
|
+
{ value: 0, label: '0%' },
|
|
2594
|
+
{ value: 50, label: '50%' },
|
|
2595
|
+
{ value: 100, label: '100%' },
|
|
2596
|
+
]"
|
|
2597
|
+
/>
|
|
2598
|
+
</template>
|
|
2599
|
+
```
|
|
2600
|
+
|
|
2601
|
+
---
|
|
2602
|
+
|
|
2603
|
+
### `DCodeChart`
|
|
2604
|
+
|
|
2605
|
+
Komponen chart berbasis Chart.js yang mendukung berbagai tipe grafik.
|
|
2606
|
+
|
|
2607
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2608
|
+
|------|------|---------|-----------|
|
|
2609
|
+
| `type` | `string` | `'line'` | Tipe chart: `'line'`, `'bar'`, `'pie'`, `'doughnut'` |
|
|
2610
|
+
| `modelValue` | `any` | — | Data chart (v-model) – format Chart.js |
|
|
2611
|
+
| `data` | `any` | — | Data chart (alternatif v-model) |
|
|
2612
|
+
| `options` | `any` | — | Opsi Chart.js custom |
|
|
2613
|
+
| `width` | `number` | `400` | Lebar canvas |
|
|
2614
|
+
| `height` | `number` | `300` | Tinggi canvas (px) |
|
|
2615
|
+
| `loading` | `boolean` | `false` | Status loading |
|
|
2616
|
+
| `error` | `string\|null` | `null` | Pesan error |
|
|
2617
|
+
| `showInfo` | `boolean` | `false` | Tampilkan info dataset/datapoints |
|
|
2618
|
+
| `responsive` | `boolean` | `true` | Responsif |
|
|
2619
|
+
| `maintainAspectRatio` | `boolean` | `false` | Pertahankan rasio aspek |
|
|
2620
|
+
| `theme` | `string` | `'light'` | Tema: `'light'` / `'dark'` |
|
|
2621
|
+
| `colors` | `string[]` | 10 warna default | Palet warna |
|
|
2622
|
+
| `label` | `string` | — | Label di atas chart |
|
|
2623
|
+
| `required` | `boolean` | `false` | Required indicator |
|
|
2624
|
+
|
|
2625
|
+
Events: `click`, `hover`, `ready`
|
|
2626
|
+
|
|
2627
|
+
```vue
|
|
2628
|
+
<script setup>
|
|
2629
|
+
const chartData = {
|
|
2630
|
+
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mei'],
|
|
2631
|
+
datasets: [{
|
|
2632
|
+
label: 'Penjualan',
|
|
2633
|
+
data: [120, 190, 300, 250, 400],
|
|
2634
|
+
}]
|
|
2635
|
+
}
|
|
2636
|
+
</script>
|
|
2637
|
+
|
|
2638
|
+
<template>
|
|
2639
|
+
<DCodeChart
|
|
2640
|
+
type="bar"
|
|
2641
|
+
:model-value="chartData"
|
|
2642
|
+
:height="350"
|
|
2643
|
+
label="Laporan Penjualan"
|
|
2644
|
+
theme="light"
|
|
2645
|
+
/>
|
|
2646
|
+
</template>
|
|
2647
|
+
```
|
|
2648
|
+
|
|
2649
|
+
---
|
|
2650
|
+
|
|
2651
|
+
### `DCodeWizard`
|
|
2652
|
+
|
|
2653
|
+
Komponen wizard/stepper multi-langkah dengan navigasi dan loading data per tab.
|
|
2654
|
+
|
|
2655
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
2656
|
+
|------|------|---------|-----------|
|
|
2657
|
+
| `tabs` | `WizardTab[]` | — | **Required.** Definisi tab/langkah |
|
|
2658
|
+
| `modelValue` | `number\|string` | — | Index/key tab aktif (v-model) |
|
|
2659
|
+
| `defaultTab` | `number` | — | Tab default yang aktif |
|
|
2660
|
+
| `allowTabClick` | `boolean` | — | Izinkan klik langsung ke tab |
|
|
2661
|
+
| `disabled` | `boolean` | — | Nonaktifkan navigasi |
|
|
2662
|
+
| `cache` | `boolean` | — | Cache data tab yang sudah dimuat |
|
|
2663
|
+
| `autoFetchOnMount` | `boolean` | — | Fetch data tab aktif saat mount |
|
|
2664
|
+
| `apiOptions` | `object\|Function` | — | Opsi API tambahan |
|
|
2665
|
+
| `isVertical` | `boolean` | — | Layout vertical |
|
|
2666
|
+
| `isWizard` | `boolean` | — | Mode wizard (step-by-step, tidak bisa loncat) |
|
|
2667
|
+
| `typeWizard` | `'number'\|'icon'\|'none'\|null` | — | Tipe indikator langkah |
|
|
2668
|
+
| `showNavigation` | `boolean` | — | Tampilkan tombol navigasi Prev/Next |
|
|
2669
|
+
| `submitText` | `string` | — | Teks tombol submit di langkah terakhir |
|
|
2670
|
+
| `loading` | `boolean` | — | Loading eksternal |
|
|
2671
|
+
|
|
2672
|
+
**`WizardTab` type:**
|
|
2673
|
+
```ts
|
|
2674
|
+
type WizardTab = {
|
|
2675
|
+
key: string
|
|
2676
|
+
title: string
|
|
2677
|
+
description?: string
|
|
2678
|
+
icon?: Component
|
|
2679
|
+
disabled?: boolean
|
|
2680
|
+
endpoint?: { apiUrl?, apiMethod?, apiModule?, apiAction? }
|
|
2681
|
+
fetchData?: () => Promise<any>
|
|
2682
|
+
isDataTable?: boolean
|
|
2683
|
+
}
|
|
2684
|
+
```
|
|
2685
|
+
|
|
2686
|
+
```vue
|
|
2687
|
+
<script setup>
|
|
2688
|
+
import { ref } from 'vue'
|
|
2689
|
+
const steps = [
|
|
2690
|
+
{ key: 'info', title: 'Info Dasar' },
|
|
2691
|
+
{ key: 'detail', title: 'Detail' },
|
|
2692
|
+
{ key: 'review', title: 'Review' },
|
|
2693
|
+
]
|
|
2694
|
+
const activeStep = ref(0)
|
|
2695
|
+
</script>
|
|
2696
|
+
|
|
2697
|
+
<template>
|
|
2698
|
+
<DCodeWizard
|
|
2699
|
+
v-model="activeStep"
|
|
2700
|
+
:tabs="steps"
|
|
2701
|
+
is-wizard
|
|
2702
|
+
type-wizard="number"
|
|
2703
|
+
:show-navigation="true"
|
|
2704
|
+
submit-text="Selesai"
|
|
2705
|
+
@submit="handleSubmit"
|
|
2706
|
+
>
|
|
2707
|
+
<template #info>
|
|
2708
|
+
<!-- Konten step Info Dasar -->
|
|
2709
|
+
</template>
|
|
2710
|
+
<template #detail>
|
|
2711
|
+
<!-- Konten step Detail -->
|
|
2712
|
+
</template>
|
|
2713
|
+
<template #review>
|
|
2714
|
+
<!-- Konten step Review -->
|
|
2715
|
+
</template>
|
|
2716
|
+
</DCodeWizard>
|
|
2717
|
+
</template>
|
|
2718
|
+
```
|
|
2719
|
+
|
|
2720
|
+
---
|
|
2721
|
+
|
|
2722
|
+
### `DCodeTimelineWithIcons`
|
|
2723
|
+
|
|
2724
|
+
Komponen timeline interaktif dengan berbagai tipe item (flight, interview, dll.).
|
|
2725
|
+
|
|
2726
|
+
Komponen ini merender timeline visual dari array item yang sudah terdefinisi di dalamnya. Cocok digunakan untuk menampilkan riwayat proses, activity log, atau alur perjalanan.
|
|
2727
|
+
|
|
2728
|
+
> ℹ️ Komponen ini tidak memiliki props eksternal — data dan tampilan dikonfigurasi secara internal. Gunakan sebagai komponen display/demo timeline.
|
|
2729
|
+
|
|
2730
|
+
```vue
|
|
2731
|
+
<template>
|
|
2732
|
+
<DCodeTimelineWithIcons />
|
|
2733
|
+
</template>
|
|
2734
|
+
```
|
|
2735
|
+
|
|
2736
|
+
---
|
|
2737
|
+
|
|
2738
|
+
## Lisensi
|
|
2739
|
+
|
|
2740
|
+
MIT © [Gema Fajar Ramadhan](https://github.com/gemafajarramadhan)
|