@gemafajarramadhan/dynamic-ui 1.0.0 → 1.0.1
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 +859 -98
- package/dist/dynamic-ui.css +1 -1
- package/dist/dynamic-ui.es.js +14330 -0
- package/dist/dynamic-ui.umd.js +48 -0
- package/package.json +36 -7
- package/dist/dynamic-ui.js +0 -65087
- package/dist/dynamic-ui.umd.cjs +0 -78
package/README.md
CHANGED
|
@@ -1,164 +1,925 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gemafajarramadhan/dynamic-ui
|
|
2
2
|
|
|
3
|
-
Library
|
|
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
|
+
- [Komponen Base (Vue Components)](#komponen-base-vue-components)
|
|
12
|
+
- [Daftar Komponen](#daftar-komponen)
|
|
13
|
+
- [Penggunaan di Vue 3 (Import Per Komponen)](#penggunaan-di-vue-3-import-per-komponen)
|
|
14
|
+
- [Penggunaan di Vue 3 (Global Plugin)](#penggunaan-di-vue-3-global-plugin)
|
|
15
|
+
- [Penggunaan di Nuxt 3](#penggunaan-di-nuxt-3-vue-components)
|
|
16
|
+
- [Web Components (Framework Agnostic)](#web-components-framework-agnostic)
|
|
17
|
+
- [API Referensi Web Components](#api-referensi-web-components)
|
|
18
|
+
- [Logic Registry API](#logic-registry-api)
|
|
19
|
+
- [Penggunaan di Berbagai Framework](#penggunaan-di-berbagai-framework)
|
|
20
|
+
- [Vanilla HTML / JavaScript](#1-vanilla-html--javascript)
|
|
21
|
+
- [Vue 3 (Web Components)](#2-vue-3-web-components)
|
|
22
|
+
- [React](#3-react)
|
|
23
|
+
- [Next.js (App Router)](#4-nextjs-app-router)
|
|
24
|
+
- [Angular](#5-angular)
|
|
25
|
+
- [Nuxt 3 (Web Components)](#6-nuxt-3-web-components)
|
|
26
|
+
- [Custom Logic Registry](#custom-logic-registry)
|
|
27
|
+
- [TypeScript Support](#typescript-support)
|
|
28
|
+
|
|
29
|
+
---
|
|
4
30
|
|
|
5
31
|
## Fitur
|
|
6
32
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
-
|
|
10
|
-
- **
|
|
33
|
+
- 🧩 **Web Components** – Dapat digunakan di framework **apapun** (React, Vue, Angular, Svelte, dll.) atau di HTML biasa.
|
|
34
|
+
- 📋 **AutoLayoutForm** – Generate form otomatis dari JSON schema dengan validasi dan konfigurasi fleksibel.
|
|
35
|
+
- 📊 **AutoLayoutDatatable** – Generate tabel dengan pagination, sorting, filtering, dan action button dari JSON.
|
|
36
|
+
- 🔌 **Logic Registry** – Daftarkan custom business logic dari aplikasi host tanpa perlu memodifikasi library.
|
|
37
|
+
- 🎨 **Style Bundled** – CSS sudah ter-bundle, cukup import satu file style.
|
|
38
|
+
- 🌐 **i18n Ready** – Mendukung internasionalisasi (vue-i18n).
|
|
39
|
+
|
|
40
|
+
---
|
|
11
41
|
|
|
12
42
|
## Instalasi
|
|
13
43
|
|
|
14
44
|
```bash
|
|
15
|
-
npm
|
|
45
|
+
# npm
|
|
46
|
+
npm install @gemafajarramadhan/dynamic-ui
|
|
47
|
+
|
|
48
|
+
# yarn
|
|
49
|
+
yarn add @gemafajarramadhan/dynamic-ui
|
|
50
|
+
|
|
51
|
+
# pnpm
|
|
52
|
+
pnpm add @gemafajarramadhan/dynamic-ui
|
|
16
53
|
```
|
|
17
54
|
|
|
18
|
-
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Komponen Base (Vue Components)
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
### Daftar Komponen
|
|
62
|
+
|
|
63
|
+
| Komponen | Deskripsi |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `DCodeTextField` | Input text dengan validasi, text case, filter karakter |
|
|
66
|
+
| `DCodeTextarea` | Textarea dengan auto-resize dan validasi |
|
|
67
|
+
| `DCodeButton` | Tombol dengan variant, warna kustom, tooltip, dan animasi |
|
|
68
|
+
| `DCodeCard` | Kartu container dengan styling bawaan |
|
|
69
|
+
| `DCodeChekbox` | Checkbox dengan label |
|
|
70
|
+
| `DCodeSwitch` | Toggle switch |
|
|
71
|
+
| `DCodeRadioCustom` | Radio button dengan opsi kustom |
|
|
72
|
+
| `DCodeLabel` | Label untuk form |
|
|
73
|
+
| `DCodeAutoComplete` | Input autocomplete dengan pencarian |
|
|
74
|
+
| `DCodeMultiSelect` | Dropdown multi-pilihan |
|
|
75
|
+
| `DCodeDatePicker` | Date picker kalender |
|
|
76
|
+
| `DCodeDateRangePicker` | Date range picker |
|
|
77
|
+
| `DCodeCurrencyField` | Input nilai mata uang |
|
|
78
|
+
| `DCodeOtpInput` | Input OTP digit-per-digit |
|
|
79
|
+
| `DCodeFileField` | Input file dengan preview |
|
|
80
|
+
| `DCodeImageField` | Input gambar dengan preview |
|
|
81
|
+
| `DCodeDropzone` | Drag-and-drop upload area |
|
|
82
|
+
| `DCodeUploadFile` | Upload file terintegrasi |
|
|
83
|
+
| `DCodeFileResult` | Tampilan hasil file yang diupload |
|
|
84
|
+
| `DCodeImageResult` | Tampilan hasil gambar yang diupload |
|
|
85
|
+
| `DCodeImageCropperDialog` | Dialog crop gambar |
|
|
86
|
+
| `DCodeDialog` | Modal dialog |
|
|
87
|
+
| `DCodeDialogCloseBtn` | Tombol tutup untuk dialog |
|
|
88
|
+
| `DCodeIconDropdown` | Dropdown ikon |
|
|
89
|
+
| `DCodeProgressBar` | Progress bar loading |
|
|
90
|
+
| `DCodeTimelineWithIcons` | Timeline dengan ikon |
|
|
91
|
+
| `DCodeWizard` | Wizard / stepper multi-langkah |
|
|
19
92
|
|
|
20
|
-
|
|
93
|
+
---
|
|
21
94
|
|
|
22
|
-
|
|
95
|
+
### Penggunaan di Vue 3 (Import Per Komponen)
|
|
23
96
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
97
|
+
Cara paling ringan: import hanya komponen yang diperlukan.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install @gemafajarramadhan/dynamic-ui
|
|
27
101
|
```
|
|
28
102
|
|
|
29
|
-
|
|
103
|
+
```vue
|
|
104
|
+
<!-- MyForm.vue -->
|
|
105
|
+
<script setup>
|
|
106
|
+
import { DCodeTextField, DCodeButton } from '@gemafajarramadhan/dynamic-ui'
|
|
107
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
108
|
+
</script>
|
|
30
109
|
|
|
31
|
-
|
|
110
|
+
<template>
|
|
111
|
+
<form>
|
|
112
|
+
<DCodeTextField
|
|
113
|
+
v-model="form.name"
|
|
114
|
+
label="Nama Lengkap"
|
|
115
|
+
placeholder="Masukkan nama..."
|
|
116
|
+
:clearable="true"
|
|
117
|
+
/>
|
|
118
|
+
|
|
119
|
+
<DCodeTextField
|
|
120
|
+
v-model="form.email"
|
|
121
|
+
label="Email"
|
|
122
|
+
valueType="email"
|
|
123
|
+
placeholder="nama@email.com"
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
<DCodeButton
|
|
127
|
+
text="Simpan"
|
|
128
|
+
variant="default"
|
|
129
|
+
bgColor="primary"
|
|
130
|
+
type="submit"
|
|
131
|
+
/>
|
|
132
|
+
</form>
|
|
133
|
+
</template>
|
|
134
|
+
|
|
135
|
+
<script setup>
|
|
136
|
+
import { reactive } from 'vue'
|
|
137
|
+
|
|
138
|
+
const form = reactive({ name: '', email: '' })
|
|
139
|
+
</script>
|
|
140
|
+
```
|
|
32
141
|
|
|
33
|
-
|
|
34
|
-
<micro-dynamic-form id="myForm"></micro-dynamic-form>
|
|
142
|
+
---
|
|
35
143
|
|
|
36
|
-
|
|
37
|
-
const form = document.querySelector("#myForm");
|
|
144
|
+
### Penggunaan di Vue 3 (Global Plugin)
|
|
38
145
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
146
|
+
Install semua komponen sekaligus agar bisa digunakan di mana saja tanpa import per file.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
// main.js
|
|
150
|
+
import { createApp } from 'vue'
|
|
151
|
+
import App from './App.vue'
|
|
152
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
153
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
154
|
+
|
|
155
|
+
const app = createApp(App)
|
|
156
|
+
app.use(DynamicUI) // Mendaftarkan semua DCode* komponen secara global
|
|
157
|
+
app.mount('#app')
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Setelah itu, semua komponen bisa digunakan **tanpa import** di setiap file:
|
|
161
|
+
|
|
162
|
+
```vue
|
|
163
|
+
<!-- Tidak perlu import lagi! -->
|
|
164
|
+
<template>
|
|
165
|
+
<DCodeTextField v-model="name" label="Nama" />
|
|
166
|
+
<DCodeButton text="Submit" />
|
|
167
|
+
<DCodeDatePicker v-model="date" label="Tanggal" />
|
|
168
|
+
</template>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### Penggunaan di Nuxt 3 (Vue Components)
|
|
174
|
+
|
|
175
|
+
Buat plugin Nuxt untuk mendaftarkan semua komponen secara global:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// plugins/dynamic-ui.ts
|
|
179
|
+
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
|
|
180
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
181
|
+
|
|
182
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
183
|
+
nuxtApp.vueApp.use(DynamicUI)
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Kemudian gunakan di page atau component manapun:
|
|
188
|
+
|
|
189
|
+
```vue
|
|
190
|
+
<!-- pages/index.vue -->
|
|
191
|
+
<template>
|
|
192
|
+
<div>
|
|
193
|
+
<DCodeTextField v-model="search" label="Pencarian" :clearable="true" />
|
|
194
|
+
<DCodeButton text="Cari" variant="default" />
|
|
195
|
+
</div>
|
|
196
|
+
</template>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Web Components (Framework Agnostic)
|
|
202
|
+
|
|
203
|
+
Untuk digunakan di **React, Angular, Svelte**, atau framework lainnya, gunakan mode Web Components.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## API Referensi Web Components
|
|
208
|
+
|
|
209
|
+
### Web Components
|
|
210
|
+
|
|
211
|
+
Setelah library di-import, dua custom element berikut akan terdaftar secara global:
|
|
212
|
+
|
|
213
|
+
| Custom Element | Deskripsi |
|
|
214
|
+
| ------------------------- | ----------------------------------------------------------- |
|
|
215
|
+
| `<micro-dynamic-form>` | Render form dinamis berdasarkan JSON config `config` prop. |
|
|
216
|
+
| `<micro-dynamic-datatable>` | Render tabel dinamis berdasarkan JSON config `config` prop. |
|
|
217
|
+
|
|
218
|
+
#### Properties (set via JavaScript/DOM)
|
|
219
|
+
|
|
220
|
+
Karena Web Components menerima objek JavaScript (bukan string HTML attribute), set properti ini **secara imperatif via DOM** atau `ref`:
|
|
221
|
+
|
|
222
|
+
| Property | Tipe | Deskripsi |
|
|
223
|
+
| --------- | -------- | --------------------------------------- |
|
|
224
|
+
| `config` | `Object` | Konfigurasi form / tabel dalam bentuk JSON. |
|
|
225
|
+
|
|
226
|
+
#### Events
|
|
227
|
+
|
|
228
|
+
| Event | `event.detail` | Deskripsi |
|
|
229
|
+
| -------- | ------------------ | ------------------------------------------- |
|
|
230
|
+
| `submit` | `Object` (payload) | Dipanggil saat form di-submit. |
|
|
231
|
+
| `action` | `Object` (payload) | Dipanggil saat action button datatable diklik. |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Logic Registry API
|
|
236
|
+
|
|
237
|
+
Digunakan untuk menyuntikkan business logic dari aplikasi host ke dalam komponen.
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
import {
|
|
241
|
+
registerLogic,
|
|
242
|
+
unregisterLogic,
|
|
243
|
+
getLogicModule
|
|
244
|
+
} from '@gemafajarramadhan/dynamic-ui'
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
| Fungsi | Deskripsi |
|
|
248
|
+
| ----------------------------- | ----------------------------------------------------------- |
|
|
249
|
+
| `registerLogic(code, module)` | Daftarkan logic module untuk layout code tertentu. |
|
|
250
|
+
| `unregisterLogic(code)` | Hapus logic module dari registry. |
|
|
251
|
+
| `getLogicModule(code)` | Ambil logic module berdasarkan layout code. |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Cara Penggunaan
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### 1. Vanilla HTML / JavaScript
|
|
260
|
+
|
|
261
|
+
Cara paling sederhana. Import library dari CDN atau bundler, lalu gunakan custom element di HTML.
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<!DOCTYPE html>
|
|
265
|
+
<html lang="en">
|
|
266
|
+
<head>
|
|
267
|
+
<meta charset="UTF-8" />
|
|
268
|
+
<title>Dynamic UI</title>
|
|
269
|
+
<!-- Import style -->
|
|
270
|
+
<link rel="stylesheet" href="./node_modules/@gemafajarramadhan/dynamic-ui/dist/style.css" />
|
|
271
|
+
</head>
|
|
272
|
+
<body>
|
|
273
|
+
|
|
274
|
+
<!-- Dynamic Form -->
|
|
275
|
+
<micro-dynamic-form id="myForm"></micro-dynamic-form>
|
|
276
|
+
|
|
277
|
+
<!-- Dynamic DataTable -->
|
|
278
|
+
<micro-dynamic-datatable id="myTable"></micro-dynamic-datatable>
|
|
279
|
+
|
|
280
|
+
<!-- Import library (registrasi Web Components otomatis) -->
|
|
281
|
+
<script type="module">
|
|
282
|
+
import '@gemafajarramadhan/dynamic-ui';
|
|
283
|
+
|
|
284
|
+
// --- Setup Form ---
|
|
285
|
+
const form = document.querySelector('#myForm');
|
|
286
|
+
form.config = {
|
|
287
|
+
title: 'Registrasi User',
|
|
288
|
+
sections: [
|
|
52
289
|
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
290
|
+
title: 'Info Pribadi',
|
|
291
|
+
fields: [
|
|
292
|
+
{
|
|
293
|
+
key: 'name',
|
|
294
|
+
label: 'Nama Lengkap',
|
|
295
|
+
model: 'fullName',
|
|
296
|
+
component: 'DCodeTextField',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
key: 'email',
|
|
300
|
+
label: 'Email',
|
|
301
|
+
model: 'email',
|
|
302
|
+
component: 'DCodeTextField',
|
|
303
|
+
props: { type: 'email' },
|
|
304
|
+
},
|
|
305
|
+
],
|
|
58
306
|
},
|
|
59
307
|
],
|
|
308
|
+
actions: [{ key: 'submit', label: 'Daftar', component: 'DCodeButton' }],
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
form.addEventListener('submit', (e) => {
|
|
312
|
+
console.log('Data Form:', e.detail);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// --- Setup DataTable ---
|
|
316
|
+
const table = document.querySelector('#myTable');
|
|
317
|
+
table.config = {
|
|
318
|
+
titleID: 'Daftar User',
|
|
319
|
+
headers: [
|
|
320
|
+
{ text: 'Nama', value: 'name' },
|
|
321
|
+
{ text: 'Email', value: 'email' },
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
table.addEventListener('action', (e) => {
|
|
326
|
+
console.log('Action:', e.detail);
|
|
327
|
+
});
|
|
328
|
+
</script>
|
|
329
|
+
|
|
330
|
+
</body>
|
|
331
|
+
</html>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
### 2. Vue 3
|
|
337
|
+
|
|
338
|
+
Di Vue 3, Web Components bisa dipakai langsung seperti HTML element biasa. Gunakan `ref` untuk set properti objek.
|
|
339
|
+
|
|
340
|
+
#### Konfigurasi Vite
|
|
341
|
+
|
|
342
|
+
Agar Vue tidak menganggap `micro-dynamic-*` sebagai Vue component yang belum terdaftar, tambahkan `isCustomElement` di `vite.config.js`:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
// vite.config.js
|
|
346
|
+
import { defineConfig } from 'vite'
|
|
347
|
+
import vue from '@vitejs/plugin-vue'
|
|
348
|
+
|
|
349
|
+
export default defineConfig({
|
|
350
|
+
plugins: [
|
|
351
|
+
vue({
|
|
352
|
+
template: {
|
|
353
|
+
compilerOptions: {
|
|
354
|
+
// Semua element yang dimulai dengan 'micro-' dianggap custom element
|
|
355
|
+
isCustomElement: (tag) => tag.startsWith('micro-'),
|
|
356
|
+
},
|
|
60
357
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
358
|
+
}),
|
|
359
|
+
],
|
|
360
|
+
})
|
|
361
|
+
```
|
|
64
362
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
363
|
+
#### Entry Point (`main.js`)
|
|
364
|
+
|
|
365
|
+
```js
|
|
366
|
+
// main.js
|
|
367
|
+
import { createApp } from 'vue'
|
|
368
|
+
import App from './App.vue'
|
|
369
|
+
|
|
370
|
+
// Import library (registrasi Web Components)
|
|
371
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
372
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
373
|
+
|
|
374
|
+
createApp(App).mount('#app')
|
|
70
375
|
```
|
|
71
376
|
|
|
72
|
-
####
|
|
377
|
+
#### Komponen Vue (`MyForm.vue`)
|
|
73
378
|
|
|
74
|
-
```
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
379
|
+
```vue
|
|
380
|
+
<script setup>
|
|
381
|
+
import { ref, onMounted } from 'vue'
|
|
382
|
+
|
|
383
|
+
const formRef = ref(null)
|
|
384
|
+
|
|
385
|
+
const formConfig = {
|
|
386
|
+
title: 'Form User',
|
|
387
|
+
sections: [
|
|
388
|
+
{
|
|
389
|
+
fields: [
|
|
390
|
+
{
|
|
391
|
+
key: 'username',
|
|
392
|
+
label: 'Username',
|
|
393
|
+
model: 'username',
|
|
394
|
+
component: 'DCodeTextField',
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
actions: [{ key: 'save', label: 'Simpan', component: 'DCodeButton' }],
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
onMounted(() => {
|
|
403
|
+
if (formRef.value) {
|
|
404
|
+
// Set config via DOM property (bukan attribute)
|
|
405
|
+
formRef.value.config = formConfig
|
|
406
|
+
|
|
407
|
+
formRef.value.addEventListener('submit', (e) => {
|
|
408
|
+
console.log('Submitted:', e.detail)
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
})
|
|
86
412
|
</script>
|
|
413
|
+
|
|
414
|
+
<template>
|
|
415
|
+
<micro-dynamic-form ref="formRef" />
|
|
416
|
+
</template>
|
|
87
417
|
```
|
|
88
418
|
|
|
89
419
|
---
|
|
90
420
|
|
|
91
|
-
### 3.
|
|
421
|
+
### 3. React
|
|
422
|
+
|
|
423
|
+
Di React, gunakan `useRef` dan `useEffect` untuk set properti DOM secara imperatif karena React tidak meneruskan objek langsung ke Web Component attributes.
|
|
424
|
+
|
|
425
|
+
#### Entry Point (`main.jsx`)
|
|
92
426
|
|
|
93
|
-
|
|
427
|
+
```jsx
|
|
428
|
+
// main.jsx
|
|
429
|
+
import React from 'react'
|
|
430
|
+
import ReactDOM from 'react-dom/client'
|
|
431
|
+
import App from './App'
|
|
432
|
+
|
|
433
|
+
// Import library (registrasi Web Components)
|
|
434
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
435
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
436
|
+
|
|
437
|
+
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
|
|
438
|
+
```
|
|
94
439
|
|
|
95
|
-
|
|
440
|
+
#### Dynamic Form Component
|
|
96
441
|
|
|
97
442
|
```jsx
|
|
98
|
-
|
|
99
|
-
import
|
|
100
|
-
import "micro-dynamic-ui/style.css"; // Load CSS
|
|
443
|
+
// components/DynamicForm.jsx
|
|
444
|
+
import React, { useEffect, useRef } from 'react'
|
|
101
445
|
|
|
102
|
-
const
|
|
103
|
-
title:
|
|
446
|
+
const formConfig = {
|
|
447
|
+
title: 'Form via React',
|
|
104
448
|
sections: [
|
|
105
449
|
{
|
|
106
450
|
fields: [
|
|
107
451
|
{
|
|
108
|
-
key:
|
|
109
|
-
label:
|
|
110
|
-
model:
|
|
111
|
-
component:
|
|
452
|
+
key: 'username',
|
|
453
|
+
label: 'Username',
|
|
454
|
+
model: 'username',
|
|
455
|
+
component: 'DCodeTextField',
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
key: 'email',
|
|
459
|
+
label: 'Email',
|
|
460
|
+
model: 'email',
|
|
461
|
+
component: 'DCodeTextField',
|
|
462
|
+
props: { type: 'email' },
|
|
112
463
|
},
|
|
113
464
|
],
|
|
114
465
|
},
|
|
115
466
|
],
|
|
116
|
-
actions: [{ key:
|
|
117
|
-
}
|
|
467
|
+
actions: [{ key: 'submit', label: 'Kirim', component: 'DCodeButton' }],
|
|
468
|
+
}
|
|
118
469
|
|
|
119
|
-
export default function
|
|
120
|
-
const formRef = useRef(null)
|
|
470
|
+
export default function DynamicForm() {
|
|
471
|
+
const formRef = useRef(null)
|
|
121
472
|
|
|
122
473
|
useEffect(() => {
|
|
123
|
-
const el = formRef.current
|
|
124
|
-
if (el)
|
|
125
|
-
// 1. Pass Config Object langsung ke property
|
|
126
|
-
el.config = FormConfig;
|
|
127
|
-
|
|
128
|
-
// 2. Event Listener untuk handle output
|
|
129
|
-
const handleSubmit = (event) => {
|
|
130
|
-
console.log("Hasil submit:", event.detail);
|
|
131
|
-
};
|
|
474
|
+
const el = formRef.current
|
|
475
|
+
if (!el) return
|
|
132
476
|
|
|
133
|
-
|
|
477
|
+
// Set config sebagai DOM property
|
|
478
|
+
el.config = formConfig
|
|
134
479
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
480
|
+
const handleSubmit = (event) => {
|
|
481
|
+
console.log('Hasil submit:', event.detail)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
el.addEventListener('submit', handleSubmit)
|
|
485
|
+
|
|
486
|
+
// Cleanup listener saat unmount
|
|
487
|
+
return () => {
|
|
488
|
+
el.removeEventListener('submit', handleSubmit)
|
|
489
|
+
}
|
|
490
|
+
}, [])
|
|
491
|
+
|
|
492
|
+
return <micro-dynamic-form ref={formRef}></micro-dynamic-form>
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### Dynamic DataTable Component
|
|
497
|
+
|
|
498
|
+
```jsx
|
|
499
|
+
// components/DynamicTable.jsx
|
|
500
|
+
import React, { useEffect, useRef } from 'react'
|
|
501
|
+
|
|
502
|
+
const tableConfig = {
|
|
503
|
+
titleID: 'Daftar User',
|
|
504
|
+
headers: [
|
|
505
|
+
{ text: 'Nama', value: 'name' },
|
|
506
|
+
{ text: 'Email', value: 'email' },
|
|
507
|
+
{ text: 'Status', value: 'status' },
|
|
508
|
+
],
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export default function DynamicTable() {
|
|
512
|
+
const tableRef = useRef(null)
|
|
513
|
+
|
|
514
|
+
useEffect(() => {
|
|
515
|
+
const el = tableRef.current
|
|
516
|
+
if (!el) return
|
|
517
|
+
|
|
518
|
+
el.config = tableConfig
|
|
519
|
+
|
|
520
|
+
const handleAction = (event) => {
|
|
521
|
+
console.log('Action:', event.detail)
|
|
139
522
|
}
|
|
140
|
-
|
|
523
|
+
|
|
524
|
+
el.addEventListener('action', handleAction)
|
|
525
|
+
|
|
526
|
+
return () => {
|
|
527
|
+
el.removeEventListener('action', handleAction)
|
|
528
|
+
}
|
|
529
|
+
}, [])
|
|
530
|
+
|
|
531
|
+
return <micro-dynamic-datatable ref={tableRef}></micro-dynamic-datatable>
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
#### TypeScript + React
|
|
536
|
+
|
|
537
|
+
Tambahkan deklarasi tipe agar JSX tidak mengeluh tentang custom element:
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
// src/types/dynamic-ui.d.ts
|
|
541
|
+
import React from 'react'
|
|
542
|
+
|
|
543
|
+
declare global {
|
|
544
|
+
namespace JSX {
|
|
545
|
+
interface IntrinsicElements {
|
|
546
|
+
'micro-dynamic-form': React.DetailedHTMLProps<
|
|
547
|
+
React.HTMLAttributes<HTMLElement>,
|
|
548
|
+
HTMLElement
|
|
549
|
+
>
|
|
550
|
+
'micro-dynamic-datatable': React.DetailedHTMLProps<
|
|
551
|
+
React.HTMLAttributes<HTMLElement>,
|
|
552
|
+
HTMLElement
|
|
553
|
+
>
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
### 4. Next.js (App Router)
|
|
562
|
+
|
|
563
|
+
Di Next.js dengan App Router, Web Components harus di-import di sisi klien (`'use client'`).
|
|
564
|
+
|
|
565
|
+
#### Setup di Layout atau Client Component
|
|
566
|
+
|
|
567
|
+
```tsx
|
|
568
|
+
// components/DynamicForm.tsx
|
|
569
|
+
'use client'
|
|
570
|
+
|
|
571
|
+
import { useEffect, useRef } from 'react'
|
|
572
|
+
|
|
573
|
+
// Import hanya di sisi klien
|
|
574
|
+
import('@gemafajarramadhan/dynamic-ui')
|
|
575
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
576
|
+
|
|
577
|
+
const formConfig = {
|
|
578
|
+
title: 'Form Next.js',
|
|
579
|
+
sections: [
|
|
580
|
+
{
|
|
581
|
+
fields: [
|
|
582
|
+
{
|
|
583
|
+
key: 'name',
|
|
584
|
+
label: 'Nama',
|
|
585
|
+
model: 'name',
|
|
586
|
+
component: 'DCodeTextField',
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
actions: [{ key: 'submit', label: 'Submit', component: 'DCodeButton' }],
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export default function DynamicFormClient() {
|
|
595
|
+
const formRef = useRef<HTMLElement>(null)
|
|
596
|
+
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
const el = formRef.current
|
|
599
|
+
if (!el) return
|
|
600
|
+
|
|
601
|
+
;(el as any).config = formConfig
|
|
602
|
+
|
|
603
|
+
const handleSubmit = (event: Event) => {
|
|
604
|
+
const customEvent = event as CustomEvent
|
|
605
|
+
console.log('Submit:', customEvent.detail)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
el.addEventListener('submit', handleSubmit)
|
|
609
|
+
return () => el.removeEventListener('submit', handleSubmit)
|
|
610
|
+
}, [])
|
|
141
611
|
|
|
142
612
|
return (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
613
|
+
// @ts-ignore – custom element tidak dikenal TypeScript secara default
|
|
614
|
+
<micro-dynamic-form ref={formRef}></micro-dynamic-form>
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
#### Penggunaan di Page
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
// app/page.tsx
|
|
623
|
+
import DynamicFormClient from '@/components/DynamicForm'
|
|
624
|
+
|
|
625
|
+
export default function HomePage() {
|
|
626
|
+
return (
|
|
627
|
+
<main>
|
|
628
|
+
<h1>Dynamic Form</h1>
|
|
629
|
+
<DynamicFormClient />
|
|
630
|
+
</main>
|
|
631
|
+
)
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
> **Catatan:** Gunakan `dynamic()` dari `next/dynamic` dengan `{ ssr: false }` jika terjadi error SSR.
|
|
636
|
+
>
|
|
637
|
+
> ```tsx
|
|
638
|
+
> import dynamic from 'next/dynamic'
|
|
639
|
+
> const DynamicFormClient = dynamic(() => import('@/components/DynamicForm'), { ssr: false })
|
|
640
|
+
> ```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
### 5. Angular
|
|
645
|
+
|
|
646
|
+
Di Angular, tambahkan `CUSTOM_ELEMENTS_SCHEMA` agar Angular tidak melempar error pada custom element yang tidak dikenali.
|
|
647
|
+
|
|
648
|
+
#### `app.module.ts` (Module-based)
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
// app.module.ts
|
|
652
|
+
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
653
|
+
import { BrowserModule } from '@angular/platform-browser'
|
|
654
|
+
import { AppComponent } from './app.component'
|
|
655
|
+
|
|
656
|
+
@NgModule({
|
|
657
|
+
declarations: [AppComponent],
|
|
658
|
+
imports: [BrowserModule],
|
|
659
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA], // <-- Wajib ditambahkan
|
|
660
|
+
bootstrap: [AppComponent],
|
|
661
|
+
})
|
|
662
|
+
export class AppModule {}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
#### `main.ts`
|
|
666
|
+
|
|
667
|
+
```ts
|
|
668
|
+
// main.ts
|
|
669
|
+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
|
670
|
+
import { AppModule } from './app/app.module'
|
|
671
|
+
|
|
672
|
+
// Import library (registrasi Web Components)
|
|
673
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
674
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
675
|
+
|
|
676
|
+
platformBrowserDynamic().bootstrapModule(AppModule)
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
#### Komponen Angular (`dynamic-form.component.ts`)
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
// dynamic-form.component.ts
|
|
683
|
+
import {
|
|
684
|
+
Component,
|
|
685
|
+
OnInit,
|
|
686
|
+
AfterViewInit,
|
|
687
|
+
ViewChild,
|
|
688
|
+
ElementRef,
|
|
689
|
+
} from '@angular/core'
|
|
690
|
+
|
|
691
|
+
@Component({
|
|
692
|
+
selector: 'app-dynamic-form',
|
|
693
|
+
template: `<micro-dynamic-form #formEl></micro-dynamic-form>`,
|
|
694
|
+
})
|
|
695
|
+
export class DynamicFormComponent implements AfterViewInit {
|
|
696
|
+
@ViewChild('formEl') formRef!: ElementRef
|
|
697
|
+
|
|
698
|
+
private formConfig = {
|
|
699
|
+
title: 'Form Angular',
|
|
700
|
+
sections: [
|
|
701
|
+
{
|
|
702
|
+
fields: [
|
|
703
|
+
{
|
|
704
|
+
key: 'username',
|
|
705
|
+
label: 'Username',
|
|
706
|
+
model: 'username',
|
|
707
|
+
component: 'DCodeTextField',
|
|
708
|
+
},
|
|
709
|
+
],
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
actions: [{ key: 'submit', label: 'Simpan', component: 'DCodeButton' }],
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
ngAfterViewInit() {
|
|
716
|
+
const el = this.formRef.nativeElement
|
|
717
|
+
|
|
718
|
+
// Set config via DOM property
|
|
719
|
+
el.config = this.formConfig
|
|
720
|
+
|
|
721
|
+
el.addEventListener('submit', (event: CustomEvent) => {
|
|
722
|
+
console.log('Submit:', event.detail)
|
|
723
|
+
})
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
#### Angular Standalone (Angular 17+)
|
|
729
|
+
|
|
730
|
+
```ts
|
|
731
|
+
// app.component.ts
|
|
732
|
+
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'
|
|
733
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
734
|
+
|
|
735
|
+
@Component({
|
|
736
|
+
selector: 'app-root',
|
|
737
|
+
standalone: true,
|
|
738
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
739
|
+
template: `<micro-dynamic-form #formEl></micro-dynamic-form>`,
|
|
740
|
+
})
|
|
741
|
+
export class AppComponent implements AfterViewInit {
|
|
742
|
+
@ViewChild('formEl') formRef!: ElementRef
|
|
743
|
+
|
|
744
|
+
ngAfterViewInit() {
|
|
745
|
+
const el = this.formRef.nativeElement
|
|
746
|
+
el.config = { /* config Anda di sini */ }
|
|
747
|
+
el.addEventListener('submit', (e: CustomEvent) => console.log(e.detail))
|
|
748
|
+
}
|
|
148
749
|
}
|
|
149
750
|
```
|
|
150
751
|
|
|
151
|
-
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
### 6. Nuxt 3
|
|
755
|
+
|
|
756
|
+
Di Nuxt 3, gunakan plugin untuk mendaftarkan Web Components dan tandai sebagai custom element.
|
|
757
|
+
|
|
758
|
+
#### Plugin (`plugins/dynamic-ui.client.ts`)
|
|
759
|
+
|
|
760
|
+
Buat file plugin dengan suffix `.client.ts` agar hanya dijalankan di sisi klien (browser):
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
// plugins/dynamic-ui.client.ts
|
|
764
|
+
import '@gemafajarramadhan/dynamic-ui'
|
|
765
|
+
import '@gemafajarramadhan/dynamic-ui/style.css'
|
|
766
|
+
|
|
767
|
+
export default defineNuxtPlugin(() => {
|
|
768
|
+
// Web Components sudah terdaftar saat import di atas
|
|
769
|
+
})
|
|
770
|
+
```
|
|
152
771
|
|
|
153
|
-
|
|
772
|
+
#### Konfigurasi Nuxt (`nuxt.config.ts`)
|
|
154
773
|
|
|
155
774
|
```ts
|
|
775
|
+
// nuxt.config.ts
|
|
776
|
+
export default defineNuxtConfig({
|
|
777
|
+
vue: {
|
|
778
|
+
compilerOptions: {
|
|
779
|
+
// Arahkan Nuxt/Vue agar tidak menganggap element ini sebagai Vue component
|
|
780
|
+
isCustomElement: (tag: string) => tag.startsWith('micro-'),
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
})
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
#### Penggunaan di Page atau Component
|
|
787
|
+
|
|
788
|
+
```vue
|
|
789
|
+
<!-- pages/index.vue -->
|
|
790
|
+
<script setup>
|
|
791
|
+
import { ref, onMounted } from 'vue'
|
|
792
|
+
|
|
793
|
+
const formRef = ref(null)
|
|
794
|
+
|
|
795
|
+
const formConfig = {
|
|
796
|
+
title: 'Form Nuxt',
|
|
797
|
+
sections: [
|
|
798
|
+
{
|
|
799
|
+
fields: [
|
|
800
|
+
{
|
|
801
|
+
key: 'name',
|
|
802
|
+
label: 'Nama',
|
|
803
|
+
model: 'name',
|
|
804
|
+
component: 'DCodeTextField',
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
actions: [{ key: 'submit', label: 'Submit', component: 'DCodeButton' }],
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
onMounted(() => {
|
|
813
|
+
if (formRef.value) {
|
|
814
|
+
formRef.value.config = formConfig
|
|
815
|
+
formRef.value.addEventListener('submit', (e) => {
|
|
816
|
+
console.log('Submit:', e.detail)
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
})
|
|
820
|
+
</script>
|
|
821
|
+
|
|
822
|
+
<template>
|
|
823
|
+
<div>
|
|
824
|
+
<h1>Halaman Dynamic Form</h1>
|
|
825
|
+
<!-- Gunakan ClientOnly agar tidak error saat SSR -->
|
|
826
|
+
<ClientOnly>
|
|
827
|
+
<micro-dynamic-form ref="formRef" />
|
|
828
|
+
</ClientOnly>
|
|
829
|
+
</div>
|
|
830
|
+
</template>
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## Custom Logic Registry
|
|
836
|
+
|
|
837
|
+
Library menyediakan **Logic Registry** agar aplikasi host dapat menyuntikkan business logic ke dalam komponen tanpa memodifikasi library.
|
|
838
|
+
|
|
839
|
+
### Option A: via `window` (sebelum library dimuat)
|
|
840
|
+
|
|
841
|
+
```js
|
|
842
|
+
window.__MICRO_LOGIC_REGISTRY__ = {
|
|
843
|
+
'user-management': {
|
|
844
|
+
onReady(context) {
|
|
845
|
+
console.log('Component siap:', context)
|
|
846
|
+
},
|
|
847
|
+
handleAction(action, payload) {
|
|
848
|
+
if (action === 'delete') {
|
|
849
|
+
// Tampilkan modal konfirmasi
|
|
850
|
+
return true // intercept action
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### Option B: via fungsi `registerLogic` (setelah import library)
|
|
858
|
+
|
|
859
|
+
```js
|
|
860
|
+
import {
|
|
861
|
+
registerLogic,
|
|
862
|
+
unregisterLogic,
|
|
863
|
+
} from '@gemafajarramadhan/dynamic-ui'
|
|
864
|
+
|
|
865
|
+
registerLogic('user-management', {
|
|
866
|
+
onReady(context) {
|
|
867
|
+
console.log('Component siap, context:', context)
|
|
868
|
+
},
|
|
869
|
+
handleAction(action, payload) {
|
|
870
|
+
console.log('Action:', action, 'Payload:', payload)
|
|
871
|
+
},
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
// Untuk menghapus logic saat komponen unmount:
|
|
875
|
+
// unregisterLogic('user-management')
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### Interface LogicModule
|
|
879
|
+
|
|
880
|
+
```ts
|
|
881
|
+
interface LogicModule {
|
|
882
|
+
onReady?: (context: Record<string, any>) => void
|
|
883
|
+
handleAction?: (action: string, payload: any) => boolean | void
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
|
|
889
|
+
## TypeScript Support
|
|
890
|
+
|
|
891
|
+
Untuk menambahkan type yang benar pada semua framework, buat file deklarasi global:
|
|
892
|
+
|
|
893
|
+
```ts
|
|
894
|
+
// src/types/dynamic-ui.d.ts
|
|
895
|
+
|
|
896
|
+
declare namespace DynamicUI {
|
|
897
|
+
interface LogicModule {
|
|
898
|
+
onReady?: (context: Record<string, any>) => void
|
|
899
|
+
handleAction?: (action: string, payload: any) => boolean | void
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Deklarasi untuk custom element di JSX (React / Next.js)
|
|
156
904
|
declare global {
|
|
157
905
|
namespace JSX {
|
|
158
906
|
interface IntrinsicElements {
|
|
159
|
-
|
|
160
|
-
|
|
907
|
+
'micro-dynamic-form': any
|
|
908
|
+
'micro-dynamic-datatable': any
|
|
161
909
|
}
|
|
162
910
|
}
|
|
911
|
+
|
|
912
|
+
// Untuk global window registry
|
|
913
|
+
interface Window {
|
|
914
|
+
__MICRO_LOGIC_REGISTRY__?: Record<string, DynamicUI.LogicModule>
|
|
915
|
+
}
|
|
163
916
|
}
|
|
917
|
+
|
|
918
|
+
export {}
|
|
164
919
|
```
|
|
920
|
+
|
|
921
|
+
---
|
|
922
|
+
|
|
923
|
+
## Lisensi
|
|
924
|
+
|
|
925
|
+
MIT © [Gema Fajar Ramadhan](https://github.com/gemafajarramadhan)
|