@gemafajarramadhan/dynamic-ui 1.0.0 → 1.0.2

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 CHANGED
@@ -1,164 +1,925 @@
1
- # Micro Dynamic UI
1
+ # @gemafajarramadhan/dynamic-ui
2
2
 
3
- Library Microfrontend ringan dan framework-agnostic untuk membuat Dynamic Form dan DataTable menggunakan Vue 3 Web Components.
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
- - **AutoLayoutForm**: Membuat form otomatis dari JSON schema.
8
- - **AutoLayoutDatatable**: Membuat tabel dengan pagination dan action dari JSON.
9
- - **Zero Dependencies** (runtime): Vue dan style sudah di-bundle.
10
- - **Components Included**: Menggunakan komponen base (`TextField`, `Button`) yang sudah tertanam.
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 install micro-dynamic-ui
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
- ## Cara Penggunaan
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
- ### 1. Import JavaScript dan Style
93
+ ---
21
94
 
22
- Di entry point aplikasi Anda (React `main.jsx`, Vue `main.js`, atau Angular):
95
+ ### Penggunaan di Vue 3 (Import Per Komponen)
23
96
 
24
- ```javascript
25
- import "micro-dynamic-ui";
26
- import "micro-dynamic-ui/style.css";
97
+ Cara paling ringan: import hanya komponen yang diperlukan.
98
+
99
+ ```bash
100
+ npm install @gemafajarramadhan/dynamic-ui
27
101
  ```
28
102
 
29
- ### 2. Penggunaan di HTML / Javascript Biasa
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
- #### Dynamic Form
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
- ```html
34
- <micro-dynamic-form id="myForm"></micro-dynamic-form>
142
+ ---
35
143
 
36
- <script>
37
- const form = document.querySelector("#myForm");
144
+ ### Penggunaan di Vue 3 (Global Plugin)
38
145
 
39
- // Set config property secara langsung
40
- form.config = {
41
- title: "Registrasi User",
42
- sections: [
43
- {
44
- title: "Info Pribadi",
45
- fields: [
46
- {
47
- key: "name",
48
- label: "Nama Lengkap",
49
- model: "fullName",
50
- component: "DCodeTextField",
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
- key: "email",
54
- label: "Email",
55
- model: "email",
56
- component: "DCodeTextField",
57
- props: { type: "email" },
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
- actions: [{ key: "submit", label: "Daftar", component: "DCodeButton" }],
63
- };
358
+ }),
359
+ ],
360
+ })
361
+ ```
64
362
 
65
- // Dengarkan event submit
66
- form.addEventListener("submit", (e) => {
67
- console.log("Data Form:", e.detail);
68
- });
69
- </script>
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
- #### Dynamic DataTable
377
+ #### Komponen Vue (`MyForm.vue`)
73
378
 
74
- ```html
75
- <micro-dynamic-datatable id="myTable"></micro-dynamic-datatable>
76
-
77
- <script>
78
- const table = document.querySelector("#myTable");
79
- table.config = {
80
- titleID: "Daftar User",
81
- headers: [
82
- { text: "Nama", value: "name" },
83
- { text: "Email", value: "email" },
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. Penggunaan di React
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
- Di React, karena sifat standar Web Components, kita tidak bisa mengoper objek JSON (seperti `config`) langsung melalui attribute JSX (karena akan terkonversi menjadi string `"[object Object]"`).
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
- Solusinya adalah menggunakan **`useRef`** untuk men-set properti `config` secara imperatif.
440
+ #### Dynamic Form Component
96
441
 
97
442
  ```jsx
98
- import React, { useEffect, useRef } from "react";
99
- import "micro-dynamic-ui"; // Registrasi Web Components
100
- import "micro-dynamic-ui/style.css"; // Load CSS
443
+ // components/DynamicForm.jsx
444
+ import React, { useEffect, useRef } from 'react'
101
445
 
102
- const FormConfig = {
103
- title: "Form via React",
446
+ const formConfig = {
447
+ title: 'Form via React',
104
448
  sections: [
105
449
  {
106
450
  fields: [
107
451
  {
108
- key: "username",
109
- label: "Username",
110
- model: "username",
111
- component: "DCodeTextField",
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: "save", label: "Simpan", component: "DCodeButton" }],
117
- };
467
+ actions: [{ key: 'submit', label: 'Kirim', component: 'DCodeButton' }],
468
+ }
118
469
 
119
- export default function ReactForm() {
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
- el.addEventListener("submit", handleSubmit);
477
+ // Set config sebagai DOM property
478
+ el.config = formConfig
134
479
 
135
- // Cleanup
136
- return () => {
137
- el.removeEventListener("submit", handleSubmit);
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
- <div>
144
- {/* Gunakan tag custom element */}
145
- <micro-dynamic-form ref={formRef}></micro-dynamic-form>
146
- </div>
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
- #### TypeScript di React ?
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
- Jika Anda menggunakan TypeScript, Anda mungkin perlu mendefinisikan elemen tersebut agar tidak error di JSX:
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
- "micro-dynamic-form": any;
160
- "micro-dynamic-datatable": any;
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)