npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gemafajarramadhan/dynamic-ui

v1.1.34

Published

Vue 3 Dynamic UI Component Library - Compatible with Vue, React, Angular, and any other framework via Web Components

Downloads

4,389

Readme

@gemafajarramadhan/dynamic-ui

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.


Daftar Isi


Fitur

  • 🧩 Web Components – Dapat digunakan di framework apapun (React, Vue, Angular, Svelte, dll.) atau di HTML biasa.
  • 📋 AutoLayoutForm – Generate form otomatis dari JSON schema dengan validasi dan konfigurasi fleksibel.
  • 📊 AutoLayoutDatatable – Generate tabel dengan pagination, sorting, filtering, dan action button dari JSON.
  • 🔌 Logic Registry – Daftarkan custom business logic dari aplikasi host tanpa perlu memodifikasi library.
  • 🎨 Style Bundled – CSS sudah ter-bundle, cukup import satu file style.
  • 🌐 i18n Ready – Mendukung internasionalisasi (vue-i18n).
  • 🔐 API Config – Base URL dan auth token diambil dari host project, bukan dari env package ini.

Instalasi

# npm
npm install @gemafajarramadhan/dynamic-ui

# yarn
yarn add @gemafajarramadhan/dynamic-ui

# pnpm
pnpm add @gemafajarramadhan/dynamic-ui

⚡ Konfigurasi API (WAJIB)

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.

Library menggunakan sistem konfigurasi terpusat via setApiConfig() (atau via Vue Plugin). Berikut urutan prioritas resolusi base URL:

| Prioritas | Sumber | |-----------|--------| | 1️⃣ Tertinggi | apiBaseUrl via plugin / setApiConfig() | | 2️⃣ | window.__MICRO_API_BASE_URL__ (global) | | 3️⃣ | import.meta.env.VITE_API_BASE_URL (env host) | | 4️⃣ Terendah | '' (same-origin fallback) |


Konfigurasi API di Vue 3

Cara paling direkomendasikan: lewatkan config saat app.use().

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

const app = createApp(App)

app.use(DynamicUI, {
  // ✅ Base URL API dari env HOST project
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL,

  // ✅ Token auth terkini (dipanggil setiap request)
  getAuthToken: () => localStorage.getItem('access_token'),

  // ✅ Atau gunakan custom headers penuh (override Authorization)
  // getHeaders: () => ({
  //   Authorization: `Bearer ${store.getters.token}`,
  //   'X-App-ID': 'my-app',
  // }),

  // ✅ Callback ketika response 401 (Unauthorized)
  onUnauthorized: () => {
    router.push('/login')
  },
})

app.mount('#app')

File .env di host project Anda:

VITE_API_BASE_URL=https://api.example.com

Konfigurasi API di Nuxt 3

// plugins/dynamic-ui.ts
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig()

  nuxtApp.vueApp.use(DynamicUI, {
    // Ambil dari runtimeConfig Nuxt
    apiBaseUrl: config.public.apiBaseUrl,

    getAuthToken: () => useCookie('access_token').value ?? null,

    onUnauthorized: () => {
      navigateTo('/login')
    },
  })
})

File nuxt.config.ts di host project:

export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      apiBaseUrl: process.env.VITE_API_BASE_URL, // set di .env
    },
  },
})

Konfigurasi API di React / Next.js

Gunakan fungsi setApiConfig() secara langsung (tidak perlu Vue plugin).

// src/lib/dynamic-ui-config.ts (jalankan sebelum render app)
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'

setApiConfig({
  apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL,

  getAuthToken: () => localStorage.getItem('access_token'),

  onUnauthorized: () => {
    window.location.href = '/login'
  },
})

Panggil di entry point:

// app/layout.tsx atau _app.tsx
'use client'
import '@/lib/dynamic-ui-config' // ← jalankan konfigurasi lebih dulu
import '@gemafajarramadhan/dynamic-ui/style.css'

File .env.local di host project Next.js:

NEXT_PUBLIC_API_BASE_URL=https://api.example.com

Konfigurasi API di Angular

// main.ts
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

// Konfigurasi sebelum bootstrap
setApiConfig({
  apiBaseUrl: (window as any).__env?.API_BASE_URL || 'https://api.example.com',

  getAuthToken: () => localStorage.getItem('access_token'),

  onUnauthorized: () => {
    window.location.href = '/login'
  },
})

platformBrowserDynamic().bootstrapModule(AppModule)

Konfigurasi API via Window Global

Alternatif tanpa import fungsi – set sebelum library dimuat:

<script>
  // Set sebelum import library
  window.__MICRO_API_BASE_URL__ = 'https://api.example.com'
  window.__MICRO_AUTH_TOKEN__ = 'your-auth-token' // atau ambil dari cookie/localStorage
</script>
<script type="module" src="./main.js"></script>

Komponen Base (Vue Components)

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.

Daftar Komponen

| Komponen | Deskripsi | |---|---| | DCodeTextField | Input text dengan validasi, text case, filter karakter | | DCodeTextarea | Textarea dengan auto-resize dan validasi | | DCodeButton | Tombol dengan variant, warna kustom, tooltip, dan animasi | | DCodeCard | Kartu container dengan styling bawaan | | DCodeCheckbox | Checkbox dengan label | | DCodeSwitch | Toggle switch | | DCodeRadioCustom | Radio button dengan opsi kustom | | DCodeLabel | Label untuk form | | DCodeAutoComplete | Input autocomplete dengan pencarian dan fetch API otomatis | | DCodeMultiSelect | Dropdown multi-pilihan | | DCodeDatePicker | Date picker kalender | | DCodeCurrencyField | Input nilai mata uang | | DCodeOtpInput | Input OTP digit-per-digit | | DCodeFileField | Input file dengan preview | | DCodeImageField | Input gambar dengan preview | | DCodeDropzone | Drag-and-drop upload area | | DCodeUploadFile | Upload file terintegrasi | | DCodeFileResult | Tampilan hasil file yang diupload | | DCodeImageResult | Tampilan hasil gambar yang diupload | | DCodeImageCropperDialog | Dialog crop gambar | | DCodeDialog | Modal dialog | | DCodeDialogCloseBtn | Tombol tutup untuk dialog | | DCodeIconDropdown | Dropdown ikon | | DCodeProgressBar | Progress bar loading | | DCodeTimelineWithIcons | Timeline dengan ikon | | DCodeWizard | Wizard / stepper multi-langkah |


Penggunaan di Vue 3 (Import Per Komponen)

Cara paling ringan: import hanya komponen yang diperlukan.

<!-- MyForm.vue -->
<script setup>
import { DCodeTextField, DCodeButton } from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'
</script>

<template>
  <form>
    <DCodeTextField
      v-model="form.name"
      label="Nama Lengkap"
      placeholder="Masukkan nama..."
      :clearable="true"
    />

    <DCodeTextField
      v-model="form.email"
      label="Email"
      valueType="email"
      placeholder="[email protected]"
    />

    <DCodeButton
      text="Simpan"
      variant="default"
      bgColor="primary"
      type="submit"
    />
  </form>
</template>

<script setup>
import { reactive } from 'vue'

const form = reactive({ name: '', email: '' })
</script>

Penggunaan di Vue 3 (Global Plugin)

Install semua komponen sekaligus agar bisa digunakan di mana saja tanpa import per file.

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

const app = createApp(App)

app.use(DynamicUI, {
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
  getAuthToken: () => localStorage.getItem('access_token'),
})

app.mount('#app')

Setelah itu, semua komponen bisa digunakan tanpa import di setiap file:

<!-- Tidak perlu import lagi! -->
<template>
  <DCodeTextField v-model="name" label="Nama" />
  <DCodeButton text="Submit" />
  <DCodeDatePicker v-model="date" label="Tanggal" />
</template>

Penggunaan di Nuxt 3 (Vue Components)

Buat plugin Nuxt untuk mendaftarkan semua komponen secara global:

// plugins/dynamic-ui.ts
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig()

  nuxtApp.vueApp.use(DynamicUI, {
    apiBaseUrl: config.public.apiBaseUrl,
    getAuthToken: () => useCookie('access_token').value ?? null,
  })
})

Kemudian gunakan di page atau component manapun:

<!-- pages/index.vue -->
<template>
  <div>
    <DCodeTextField v-model="search" label="Pencarian" :clearable="true" />
    <DCodeButton text="Cari" variant="default" />
  </div>
</template>

DCodeDataTable – Tabel Dinamis

DCodeDataTable adalah komponen tabel dinamis yang mendukung pagination server-side, sorting, filtering, action buttons, dan badge kondisional — seluruhnya dikonfigurasi via JSON schema.

Komponen ini dirancang sebagai micro-frontend: consumer bertanggung jawab penuh atas HTTP client, auth token, dan sistem permission.


Props Utama

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | table-config | Object | — | Config JSON (kolom, filter, action, pagination) | | fetch-handler | (params) => Promise<any> | — | [MFE] Consumer kontrol penuh fetch. Component tidak hit endpoint sama sekali. | | http-client | ({ url, method, data }) => Promise<any> | — | [MFE] Axios instance consumer untuk action tombol row/header (DELETE, PATCH, dll.) | | action-access-checker | (path, action) => boolean | () => true | [MFE] Fungsi RBAC dari consumer. Default: semua action allow. | | ignore-permissions | boolean | false | Matikan filter permission sepenuhnya | | data | any[] | [] | Data statis (tanpa API); pakai jika consumer sudah fetch di luar | | loading | boolean | false | Loading state untuk mode data statis | | columns | TableColumn[] | [] | Kolom manual (tanpa table-config) | | per-page | number | 10 | Jumlah baris per halaman awal | | show-pagination | boolean | true | Tampilkan pagination | | has-actions | boolean | false | Tampilkan kolom aksi (untuk mode kolom manual) | | api-url | string | — | URL endpoint langsung (tanpa table-config) | | api-method | string | 'POST' | HTTP method untuk apiUrl | | api-params | object | {} | Parameter tetap yang selalu disertakan di setiap request |

Events

| Event | Payload | Deskripsi | |-------|---------|-----------| | @action | { action, row, selection? } | Dipanggil saat tombol action diklik | | @update:selection | any[] | Dipanggil saat checkbox selection berubah | | @update:page | number | Dipanggil saat halaman berganti |


Pola 1 — fetchHandler ⭐ Disarankan untuk MFE

Consumer kontrol penuh HTTP. Component tidak hit endpoint sama sekali — cocok untuk menghindari error 401.

<template>
  <DCodeDataTable
    :table-config="tableConfig"
    :fetch-handler="fetchUsers"
    :http-client="httpClient"
    :action-access-checker="checkPermission"
    @action="handleAction"
  />
</template>

<script setup lang="ts">
import { DCodeDataTable } from '@gemafajarramadhan/dynamic-ui'
import axios from '@/lib/axios' // axios instance consumer — interceptor token sudah terpasang

// 1. fetchHandler: dipanggil tiap page/filter/sort/search change
//    params: { page, perPage, keyword?, ...filters }
const fetchUsers = async (params: Record<string, any>) => {
  const { data } = await axios.post('/api/users/list', params)
  return data
}

// 2. httpClient: untuk call dari tombol action row (delete, toggle, dll.)
const httpClient = async ({ url, method, data }: any) => {
  const res = await axios({ url, method, data })
  return res.data
}

// 3. Permission checker dari sistem RBAC consumer
const checkPermission = (path: string, action: string): boolean => {
  return useAuthStore().can(`${path}:${action}`)
}

// 4. Handle action event dari tabel
const handleAction = ({ action, row, selection }: any) => {
  if (action === 'edit')   router.push(`/users/${row.id}/edit`)
  if (action === 'delete') deleteUser(row.id)
  if (action === 'export') exportSelected(selection)
}

// 5. Konfigurasi tabel via JSON schema
const tableConfig = {
  headers: [
    { key: 'no',    labelID: 'No',    labelEN: 'No',    align: 'CENTER' },
    { key: 'name',  labelID: 'Nama',  labelEN: 'Name',  align: 'LEFT' },
    { key: 'email', labelID: 'Email', labelEN: 'Email', align: 'LEFT' },
    {
      key: 'status',
      labelID: 'Status', labelEN: 'Status',
      custom: {
        type: 'BADGE',
        conditionBadge: [
          { value: 'active',   labelID: 'Aktif',    labelEN: 'Active',   color: 'success' },
          { value: 'inactive', labelID: 'Nonaktif', labelEN: 'Inactive', color: 'danger' },
        ],
      },
    },
    {
      key: 'action',
      labelID: 'Aksi', labelEN: 'Actions',
      custom: {
        type: 'ACTION',
        actionCustom: [
          {
            permission: 'EDIT',
            propsActionCustom: {
              labelID: 'Edit', labelEN: 'Edit',
              icon: 'SquarePen', actionType: 'edit', bgColor: 'primary',
            },
          },
          {
            permission: 'DELETE',
            requireConfirm: true,
            propCustomMessage: {
              titleID: 'Hapus Data', titleEN: 'Delete',
              messageID: 'Yakin ingin menghapus?', messageEN: 'Are you sure?',
            },
            propsActionCustom: {
              labelID: 'Hapus', labelEN: 'Delete',
              icon: 'Trash2', actionType: 'delete', bgColor: 'red',
            },
          },
        ],
      },
    },
  ],
  filters: [
    {
      seq: 1, model: 'name', labelID: 'Nama', labelEN: 'Name', type: 'TEXT',
    },
    {
      seq: 2, model: 'status', labelID: 'Status', labelEN: 'Status', type: 'SELECT',
      propsFilters: {
        items: [
          { value: 'active',   label: 'Active' },
          { value: 'inactive', label: 'Inactive' },
        ],
      },
    },
  ],
  actionGlobal: [
    {
      permission: 'CREATE',
      propsActionGlobal: {
        labelID: 'Tambah', labelEN: 'Add New',
        icon: 'Plus', variant: 'default',
        route: '/users/create',
      },
    },
  ],
  pagination: true,
}
</script>

Pola 2 — Static Data (consumer fetch di luar)

Pakai ketika consumer sudah fetch data sendiri dan ingin sekadar menampilkan tabel.

<template>
  <DCodeDataTable
    :columns="columns"
    :data="users"
    :loading="isLoading"
    has-actions
    ignore-permissions
    @action="handleAction"
  />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from '@/lib/axios'

const users = ref([])
const isLoading = ref(false)

onMounted(async () => {
  isLoading.value = true
  const { data } = await axios.get('/api/users')
  users.value = data.data
  isLoading.value = false
})

const columns = [
  { key: 'no',    label: 'No',    align: 'center' },
  { key: 'name',  label: 'Nama',  align: 'left' },
  { key: 'email', label: 'Email', align: 'left' },
]
</script>

Pola 3 — Realtime Socket (consumer kelola socket)

Consumer connect ke WebSocket sendiri, push data ke tabel melalui :data.

<template>
  <DCodeDataTable :columns="columns" :data="realtimeData" ignore-permissions />
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { io } from 'socket.io-client'

const realtimeData = ref([])
let socket: any = null

onMounted(() => {
  socket = io('wss://your-server.com', {
    auth: { token: localStorage.getItem('token') }
  })
  socket.emit('dashboard:list', { page: 1, perPage: 10 })
  socket.on('dashboard:result', (res: any) => {
    realtimeData.value = res.data ?? res
  })
})

onUnmounted(() => socket?.disconnect())
</script>

Format Response yang Didukung

Component secara otomatis mendeteksi berbagai format response API:

{ "data": [], "totalData": 100 }    ✅ (disarankan)
{ "data": [], "maxPage": 10 }       ✅
{ "items": [], "total": 100 }       ✅
{ "list": [], "total": 100 }        ✅
{ "rows": [], "count": 100 }        ✅
{ "result": [], "total": 100 }      ✅
[]                                  ✅ (array langsung)

Expose Methods (via ref)

const tableRef = ref()

tableRef.value.fetchData()      // Refresh data (panggil ulang fetchHandler)
tableRef.value.handleRefresh()  // Reset filter & refresh
tableRef.value.resetFilters()   // Reset nilai filter saja (tanpa fetch)
console.log(tableRef.value.filters) // Reactive nilai filter saat ini
<DCodeDataTable ref="tableRef" :fetch-handler="fetchUsers" ... />

DCodeAutoComplete – Komponen dengan API

DCodeAutoComplete adalah komponen autocomplete yang bisa langsung fetch data dari API menggunakan konfigurasi yang sudah di-set di host project.

⚠️ Pastikan sudah mengatur Konfigurasi API sebelum menggunakan fitur endpoint.

Perilaku Default

  • Setiap fetch otomatis mengirimkan page=1 dan perPage=100 sebagai query parameter default.
  • Nilai ini bisa di-override via prop apiParams.

Props Utama

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | any | — | Value terpilih (v-model) | | endpoint.apiUrl | string | — | URL endpoint API (path atau full URL) | | endpoint.apiMethod | string | 'GET' | HTTP method | | apiParams | object | {} | Query params tambahan (di-merge & bisa override default) | | itemTitle | string\|Function | 'name' | Field nama tampilan dari tiap item | | itemValue | string\|Function | 'id' | Field nilai dari tiap item | | returnObject | boolean | false | Jika true, v-model menyimpan objek penuh, bukan hanya id | | clearable | boolean | false | Tampilkan tombol hapus pilihan | | disabled | boolean | false | Nonaktifkan komponen | | autoFetchOnOpen | boolean | true | Fetch otomatis saat dropdown dibuka pertama kali | | autoFetchOnMount | boolean | false | Fetch otomatis saat komponen pertama kali di-mount |

Contoh Penggunaan

Fetch dari API dengan URL langsung

<template>
  <DCodeAutoComplete
    v-model="selectedId"
    label="Pilih Departemen"
    :endpoint="{ apiUrl: '/api/master/departemen', apiMethod: 'GET' }"
    item-title="nama"
    item-value="id"
    clearable
  />
</template>

Request yang dikirim: GET /api/master/departemen?page=1&perPage=100 Base URL diambil otomatis dari konfigurasi host project (apiBaseUrl).

Dengan Parameter Tambahan

<template>
  <DCodeAutoComplete
    v-model="selectedId"
    label="Pilih Jabatan"
    :endpoint="{ apiUrl: '/api/master/jabatan' }"
    :apiParams="{ idDepartemen: selectedDeptId, isActive: true }"
    item-title="namaJabatan"
    item-value="idJabatan"
  />
</template>

Request yang dikirim: GET /api/master/jabatan?page=1&perPage=100&idDepartemen=5&isActive=true

Override Default Pagination

<template>
  <!-- Ganti perPage menjadi 50 -->
  <DCodeAutoComplete
    v-model="selected"
    label="Pilih Data"
    :endpoint="{ apiUrl: '/api/data' }"
    :apiParams="{ perPage: 50 }"
    item-title="nama"
    item-value="id"
  />
</template>

Dengan Data Statis (tanpa API)

<template>
  <DCodeAutoComplete
    v-model="selected"
    label="Pilih Status"
    :items="[
      { id: 1, name: 'Aktif' },
      { id: 0, name: 'Nonaktif' },
    ]"
    item-title="name"
    item-value="id"
  />
</template>

Return Object Penuh

<script setup>
import { ref } from 'vue'

const selectedUser = ref(null) // akan berisi { id: 1, name: 'John', email: '...' }
</script>

<template>
  <DCodeAutoComplete
    v-model="selectedUser"
    label="Pilih User"
    :endpoint="{ apiUrl: '/api/users' }"
    item-title="name"
    item-value="id"
    return-object
  />
</template>

Format Response API yang Diharapkan

{
  "status": true,
  "data": {
    "data": [
      { "id": 1, "name": "Item A" },
      { "id": 2, "name": "Item B" }
    ]
  }
}

Atau format flat:

{
  "data": [
    { "id": 1, "name": "Item A" }
  ]
}

Atau array langsung:

[
  { "id": 1, "name": "Item A" }
]

Web Components (Framework Agnostic)

Untuk digunakan di React, Angular, Svelte, atau framework lainnya, gunakan mode Web Components.


API Referensi Web Components

Web Components

Setelah library di-import, dua custom element berikut akan terdaftar secara global:

| Custom Element | Deskripsi | | ------------------------- | ----------------------------------------------------------- | | <micro-dynamic-form> | Render form dinamis berdasarkan JSON config config prop. | | <micro-dynamic-datatable> | Render tabel dinamis berdasarkan JSON config config prop. | | <micro-data-table> | Render Data Table base component secara independen. Menerima props seperti columns, data, apiUrl. |

Properties (set via JavaScript/DOM)

Karena Web Components menerima objek JavaScript (bukan string HTML attribute), set properti ini secara imperatif via DOM atau ref:

| Property | Tipe | Deskripsi | | --------- | -------- | --------------------------------------- | | config | Object | Konfigurasi form / tabel dalam bentuk JSON. |

Events

| Event | event.detail | Deskripsi | | -------- | ------------------ | ------------------------------------------- | | submit | Object (payload) | Dipanggil saat form di-submit. | | action | Object (payload) | Dipanggil saat action button datatable diklik. |


Logic Registry API

Digunakan untuk menyuntikkan business logic dari aplikasi host ke dalam komponen.

import {
  registerLogic,
  unregisterLogic,
  getLogicModule
} from '@gemafajarramadhan/dynamic-ui'

| Fungsi | Deskripsi | | ----------------------------- | ----------------------------------------------------------- | | registerLogic(code, module) | Daftarkan logic module untuk layout code tertentu. | | unregisterLogic(code) | Hapus logic module dari registry. | | getLogicModule(code) | Ambil logic module berdasarkan layout code. |


Penggunaan di Berbagai Framework


1. Vanilla HTML / JavaScript

Cara paling sederhana. Import library dari bundler, lalu gunakan custom element di HTML.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Dynamic UI</title>
    <!-- Import style -->
    <link rel="stylesheet" href="./node_modules/@gemafajarramadhan/dynamic-ui/dist/style.css" />
  </head>
  <body>

    <!-- Dynamic Form -->
    <micro-dynamic-form id="myForm"></micro-dynamic-form>

    <!-- Dynamic DataTable (AutoLayout version) -->
    <micro-dynamic-datatable id="myAutoTable"></micro-dynamic-datatable>

    <!-- Base DataTable (Stand-alone version) -->
    <micro-data-table id="myBaseTable"></micro-data-table>

    <!-- Import library (registrasi Web Components otomatis) -->
    <script type="module">
      import '@gemafajarramadhan/dynamic-ui'
      import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'

      // ✅ Konfigurasi API dari host project
      setApiConfig({
        apiBaseUrl: 'https://api.example.com',
        getAuthToken: () => localStorage.getItem('access_token'),
      })

      // --- Setup Form ---
      const form = document.querySelector('#myForm')
      form.config = {
        title: 'Registrasi User',
        sections: [
          {
            title: 'Info Pribadi',
            fields: [
              {
                key: 'name',
                label: 'Nama Lengkap',
                model: 'fullName',
                component: 'DCodeTextField',
              },
              {
                key: 'email',
                label: 'Email',
                model: 'email',
                component: 'DCodeTextField',
                props: { type: 'email' },
              },
            ],
          },
        ],
        actions: [{ key: 'submit', label: 'Daftar', component: 'DCodeButton' }],
      }

      form.addEventListener('submit', (e) => {
        console.log('Data Form:', e.detail)
      })

      // --- Setup DataTable ---
      const autoTable = document.querySelector('#myAutoTable')
      autoTable.config = {
        titleID: 'Daftar User',
        headers: [
          { text: 'Nama', value: 'name' },
          { text: 'Email', value: 'email' },
        ],
      }

      autoTable.addEventListener('action', (e) => {
        console.log('Action:', e.detail)
      })

      // --- Setup Base MicroDataTable ---
      const baseTable = document.querySelector('#myBaseTable')
      // Untuk MicroDataTable (base component), Anda bisa mengirimkan prop langsung:
      baseTable.columns = [
        { key: 'name', label: 'Nama', sortable: true },
        { key: 'email', label: 'Email' }
      ]
      // Memberikan data secara statis
      baseTable.data = [
        { name: 'John Doe', email: '[email protected]' },
        { name: 'Jane Smith', email: '[email protected]' }
      ]
      
      // Atau bisa diset melalui API:
      // baseTable.apiUrl = '/api/users'
      // baseTable.apiMethod = 'GET'
    </script>

  </body>
</html>

2. Vue 3 (Web Components)

Di Vue 3, Web Components bisa dipakai langsung seperti HTML element biasa. Gunakan ref untuk set properti objek.

Konfigurasi Vite

Agar Vue tidak menganggap micro-dynamic-* sebagai Vue component yang belum terdaftar, tambahkan isCustomElement di vite.config.js:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // Semua element yang dimulai dengan 'micro-' dianggap custom element
          isCustomElement: (tag) => tag.startsWith('micro-'),
        },
      },
    }),
  ],
})

Entry Point (main.js)

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

const app = createApp(App)

// ✅ Konfigurasi API dari env host project
app.use(DynamicUI, {
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
  getAuthToken: () => localStorage.getItem('access_token'),
  onUnauthorized: () => router.push('/login'),
})

app.mount('#app')

Komponen Vue (MyForm.vue)

<script setup>
import { ref, onMounted } from 'vue'

const formRef = ref(null)

const formConfig = {
  title: 'Form User',
  sections: [
    {
      fields: [
        {
          key: 'username',
          label: 'Username',
          model: 'username',
          component: 'DCodeTextField',
        },
      ],
    },
  ],
  actions: [{ key: 'save', label: 'Simpan', component: 'DCodeButton' }],
}

onMounted(() => {
  if (formRef.value) {
    // Set config via DOM property (bukan attribute)
    formRef.value.config = formConfig

    formRef.value.addEventListener('submit', (e) => {
      console.log('Submitted:', e.detail)
    })
  }
})
</script>

<template>
  <micro-dynamic-form ref="formRef" />
</template>

3. React

Di React, gunakan useRef dan useEffect untuk set properti DOM secara imperatif karena React tidak meneruskan objek langsung ke Web Component attributes.

Entry Point (main.jsx)

// main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

// ✅ Konfigurasi API dari env host project
setApiConfig({
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
  getAuthToken: () => localStorage.getItem('access_token'),
  onUnauthorized: () => { window.location.href = '/login' },
})

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

Dynamic Form Component

// components/DynamicForm.jsx
import React, { useEffect, useRef } from 'react'

const formConfig = {
  title: 'Form via React',
  sections: [
    {
      fields: [
        {
          key: 'username',
          label: 'Username',
          model: 'username',
          component: 'DCodeTextField',
        },
        {
          key: 'email',
          label: 'Email',
          model: 'email',
          component: 'DCodeTextField',
          props: { type: 'email' },
        },
      ],
    },
  ],
  actions: [{ key: 'submit', label: 'Kirim', component: 'DCodeButton' }],
}

export default function DynamicForm() {
  const formRef = useRef(null)

  useEffect(() => {
    const el = formRef.current
    if (!el) return

    // Set config sebagai DOM property
    el.config = formConfig

    const handleSubmit = (event) => {
      console.log('Hasil submit:', event.detail)
    }

    el.addEventListener('submit', handleSubmit)

    // Cleanup listener saat unmount
    return () => {
      el.removeEventListener('submit', handleSubmit)
    }
  }, [])

  return <micro-dynamic-form ref={formRef}></micro-dynamic-form>
}

Dynamic DataTable Component

// components/DynamicTable.jsx
import React, { useEffect, useRef } from 'react'

const tableConfig = {
  titleID: 'Daftar User',
  headers: [
    { text: 'Nama', value: 'name' },
    { text: 'Email', value: 'email' },
    { text: 'Status', value: 'status' },
  ],
}

export default function DynamicTable() {
  const tableRef = useRef(null)

  useEffect(() => {
    const el = tableRef.current
    if (!el) return

    el.config = tableConfig

    const handleAction = (event) => {
      console.log('Action:', event.detail)
    }

    el.addEventListener('action', handleAction)

    return () => {
      el.removeEventListener('action', handleAction)
    }
  }, [])

  return <micro-dynamic-datatable ref={tableRef}></micro-dynamic-datatable>
}

TypeScript + React

Tambahkan deklarasi tipe agar JSX tidak mengeluh tentang custom element:

// src/types/dynamic-ui.d.ts
import React from 'react'

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'micro-dynamic-form': React.DetailedHTMLProps<
        React.HTMLAttributes<HTMLElement>,
        HTMLElement
      >
      'micro-dynamic-datatable': React.DetailedHTMLProps<
        React.HTMLAttributes<HTMLElement>,
        HTMLElement
      >
    }
  }
}

4. Next.js (App Router)

Di Next.js dengan App Router, Web Components harus di-import di sisi klien ('use client').

Setup Konfigurasi API

// lib/dynamic-ui-config.ts
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'

setApiConfig({
  apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL!,
  getAuthToken: () => localStorage.getItem('access_token'),
  onUnauthorized: () => { window.location.href = '/login' },
})

Setup di Layout atau Client Component

// components/DynamicForm.tsx
'use client'

import { useEffect, useRef } from 'react'
import '@/lib/dynamic-ui-config'
import('@gemafajarramadhan/dynamic-ui')
import '@gemafajarramadhan/dynamic-ui/style.css'

const formConfig = {
  title: 'Form Next.js',
  sections: [
    {
      fields: [
        {
          key: 'name',
          label: 'Nama',
          model: 'name',
          component: 'DCodeTextField',
        },
      ],
    },
  ],
  actions: [{ key: 'submit', label: 'Submit', component: 'DCodeButton' }],
}

export default function DynamicFormClient() {
  const formRef = useRef<HTMLElement>(null)

  useEffect(() => {
    const el = formRef.current
    if (!el) return

    ;(el as any).config = formConfig

    const handleSubmit = (event: Event) => {
      const customEvent = event as CustomEvent
      console.log('Submit:', customEvent.detail)
    }

    el.addEventListener('submit', handleSubmit)
    return () => el.removeEventListener('submit', handleSubmit)
  }, [])

  return (
    // @ts-ignore – custom element tidak dikenal TypeScript secara default
    <micro-dynamic-form ref={formRef}></micro-dynamic-form>
  )
}

Penggunaan di Page

// app/page.tsx
import DynamicFormClient from '@/components/DynamicForm'

export default function HomePage() {
  return (
    <main>
      <h1>Dynamic Form</h1>
      <DynamicFormClient />
    </main>
  )
}

Catatan: Gunakan dynamic() dari next/dynamic dengan { ssr: false } jika terjadi error SSR.

import dynamic from 'next/dynamic'
const DynamicFormClient = dynamic(() => import('@/components/DynamicForm'), { ssr: false })

File .env.local di host project:

NEXT_PUBLIC_API_BASE_URL=https://api.example.com

5. Angular

Di Angular, tambahkan CUSTOM_ELEMENTS_SCHEMA agar Angular tidak melempar error pada custom element yang tidak dikenali.

app.module.ts (Module-based)

// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // <-- Wajib ditambahkan
  bootstrap: [AppComponent],
})
export class AppModule {}

main.ts

// main.ts
import { setApiConfig } from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

// ✅ Konfigurasi API dari environment Angular
import { environment } from './environments/environment'

setApiConfig({
  apiBaseUrl: environment.apiBaseUrl,
  getAuthToken: () => localStorage.getItem('access_token'),
  onUnauthorized: () => { window.location.href = '/login' },
})

platformBrowserDynamic().bootstrapModule(AppModule)

File src/environments/environment.ts:

export const environment = {
  production: false,
  apiBaseUrl: 'https://api.example.com',
}

Komponen Angular (dynamic-form.component.ts)

// dynamic-form.component.ts
import {
  Component,
  OnInit,
  AfterViewInit,
  ViewChild,
  ElementRef,
} from '@angular/core'

@Component({
  selector: 'app-dynamic-form',
  template: `<micro-dynamic-form #formEl></micro-dynamic-form>`,
})
export class DynamicFormComponent implements AfterViewInit {
  @ViewChild('formEl') formRef!: ElementRef

  private formConfig = {
    title: 'Form Angular',
    sections: [
      {
        fields: [
          {
            key: 'username',
            label: 'Username',
            model: 'username',
            component: 'DCodeTextField',
          },
        ],
      },
    ],
    actions: [{ key: 'submit', label: 'Simpan', component: 'DCodeButton' }],
  }

  ngAfterViewInit() {
    const el = this.formRef.nativeElement

    // Set config via DOM property
    el.config = this.formConfig

    el.addEventListener('submit', (event: CustomEvent) => {
      console.log('Submit:', event.detail)
    })
  }
}

Angular Standalone (Angular 17+)

// app.component.ts
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'

@Component({
  selector: 'app-root',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `<micro-dynamic-form #formEl></micro-dynamic-form>`,
})
export class AppComponent implements AfterViewInit {
  @ViewChild('formEl') formRef!: ElementRef

  ngAfterViewInit() {
    const el = this.formRef.nativeElement
    el.config = { /* config Anda di sini */ }
    el.addEventListener('submit', (e: CustomEvent) => console.log(e.detail))
  }
}

6. Nuxt 3

Di Nuxt 3, gunakan plugin untuk mendaftarkan Web Components dan tandai sebagai custom element.

Plugin (plugins/dynamic-ui.client.ts)

Buat file plugin dengan suffix .client.ts agar hanya dijalankan di sisi klien (browser):

// plugins/dynamic-ui.client.ts
import DynamicUI from '@gemafajarramadhan/dynamic-ui'
import '@gemafajarramadhan/dynamic-ui/style.css'

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig()

  // ✅ Konfigurasi API dari runtimeConfig Nuxt (env host project)
  nuxtApp.vueApp.use(DynamicUI, {
    apiBaseUrl: config.public.apiBaseUrl,
    getAuthToken: () => useCookie('access_token').value ?? null,
    onUnauthorized: () => navigateTo('/login'),
  })
})

Konfigurasi Nuxt (nuxt.config.ts)

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      apiBaseUrl: process.env.VITE_API_BASE_URL, // set di .env
    },
  },
  vue: {
    compilerOptions: {
      // Arahkan Nuxt/Vue agar tidak menganggap element ini sebagai Vue component
      isCustomElement: (tag: string) => tag.startsWith('micro-'),
    },
  },
})

Penggunaan di Page atau Component

<!-- pages/index.vue -->
<script setup>
import { ref, onMounted } from 'vue'

const formRef = ref(null)

const formConfig = {
  title: 'Form Nuxt',
  sections: [
    {
      fields: [
        {
          key: 'name',
          label: 'Nama',
          model: 'name',
          component: 'DCodeTextField',
        },
      ],
    },
  ],
  actions: [{ key: 'submit', label: 'Submit', component: 'DCodeButton' }],
}

onMounted(() => {
  if (formRef.value) {
    formRef.value.config = formConfig
    formRef.value.addEventListener('submit', (e) => {
      console.log('Submit:', e.detail)
    })
  }
})
</script>

<template>
  <div>
    <h1>Halaman Dynamic Form</h1>
    <!-- Gunakan ClientOnly agar tidak error saat SSR -->
    <ClientOnly>
      <micro-dynamic-form ref="formRef" />
    </ClientOnly>
  </div>
</template>

Custom Logic Registry

Library menyediakan Logic Registry agar aplikasi host dapat menyuntikkan business logic ke dalam komponen tanpa memodifikasi library.

Option A: via window (sebelum library dimuat)

window.__MICRO_LOGIC_REGISTRY__ = {
  'user-management': {
    onReady(context) {
      console.log('Component siap:', context)
    },
    handleAction(action, payload) {
      if (action === 'delete') {
        // Tampilkan modal konfirmasi
        return true // intercept action
      }
    },
  },
}

Option B: via fungsi registerLogic (setelah import library)

import {
  registerLogic,
  unregisterLogic,
} from '@gemafajarramadhan/dynamic-ui'

registerLogic('user-management', {
  onReady(context) {
    console.log('Component siap, context:', context)
  },
  handleAction(action, payload) {
    console.log('Action:', action, 'Payload:', payload)
  },
})

// Untuk menghapus logic saat komponen unmount:
// unregisterLogic('user-management')

Interface LogicModule

interface LogicModule {
  onReady?: (context: Record<string, any>) => void
  handleAction?: (action: string, payload: any) => boolean | void
}

TypeScript Support

Untuk menambahkan type yang benar pada semua framework, buat file deklarasi global:

// src/types/dynamic-ui.d.ts

declare namespace DynamicUI {
  interface LogicModule {
    onReady?: (context: Record<string, any>) => void
    handleAction?: (action: string, payload: any) => boolean | void
  }
}

// Deklarasi untuk custom element di JSX (React / Next.js)
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'micro-dynamic-form': any
      'micro-dynamic-datatable': any
    }
  }

  // Untuk global window registry
  interface Window {
    __MICRO_LOGIC_REGISTRY__?: Record<string, DynamicUI.LogicModule>
    __MICRO_API_BASE_URL__?: string
    __MICRO_AUTH_TOKEN__?: string
  }
}

export {}

Referensi Props Komponen

Berikut adalah dokumentasi lengkap props untuk setiap komponen yang tersedia.


DataTable

Komponen tabel data yang mendukung mode API (fetch otomatis) maupun data statis, dengan pagination, sorting, filtering, dan action button.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | columns | TableColumn[] | [] | Definisi kolom tabel | | data | any[] | [] | Data statis (mode non-API) | | perPage | number | 10 | Jumlah baris per halaman | | loading | boolean | false | Status loading eksternal | | hasActions | boolean | false | Tampilkan kolom aksi baris | | nested | boolean | false | Mode nested (tampilan kompak tanpa header search) | | apiModule | string | — | Module API untuk fetch data | | apiAction | string | 'list' | Action API | | apiMethod | string | — | HTTP method (GET/POST) | | apiUrl | string | — | URL endpoint API langsung | | apiParams | object | {} | Parameter tambahan untuk request API | | tableConfig | any | — | Konfigurasi JSON lengkap tabel (dari backend) | | dynamicColumns | boolean | false | Gunakan kolom dinamis dari response API | | enableQuickSearch | boolean | true | Aktifkan pencarian cepat (debounce) | | showPagination | boolean | true | Tampilkan pagination | | ignorePermissions | boolean | false | Abaikan cek permission | | permissionBase | string | — | Base path untuk permission check |

<template>
  <!-- Mode API -->
  <DataTable
    api-url="/api/users"
    api-method="POST"
    :columns="[
      { key: 'name', label: 'Nama', sortable: true },
      { key: 'email', label: 'Email' },
    ]"
    :per-page="25"
  />

  <!-- Mode data statis -->
  <DataTable
    :data="myData"
    :columns="columns"
  />
</template>

DCodeButton

Tombol dengan berbagai variant, warna kustom, ikon Lucide, dan tooltip.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | text | string | '' | Teks tombol | | icon | string | — | Nama ikon Lucide (e.g. 'Save', 'Trash2') | | variant | 'default'\|'secondary'\|'destructive'\|'outline'\|'ghost'\|'link'\|'tonal'\|'elevated' | 'default' | Variant tampilan | | size | 'default'\|'sm'\|'lg'\|'icon' | 'default' | Ukuran tombol | | tooltip | string | '' | Teks tooltip | | bgColor | string | '' | Warna background kustom (tailwind/hex) | | textColor | string | '' | Warna teks kustom | | hoverBgColor | string | '' | Warna background saat hover | | borderColor | string | '' | Warna border kustom | | disabled | boolean | false | Nonaktifkan tombol | | type | 'button'\|'submit'\|'reset' | 'button' | Tipe HTML button | | visible | boolean | true | Tampilkan/sembunyikan tombol | | skeleton | boolean | false | Tampilkan skeleton loading |

<template>
  <DCodeButton text="Simpan" icon="Save" variant="default" />
  <DCodeButton text="Hapus" icon="Trash2" variant="destructive" tooltip="Hapus data ini" />
  <DCodeButton text="Submit" type="submit" :disabled="isLoading" />
</template>

DCodeTextField

Input teks dengan banyak fitur: filter karakter, transformasi case, clearable, password toggle.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | string\|number\|null | — | Value input (v-model) | | prependInner | string | — | Teks/ikon di sisi kiri input | | clearable | boolean | — | Tampilkan tombol clear | | hideClearWhenEmpty | boolean | — | Sembunyikan tombol clear saat kosong | | textCase | 'none'\|'uppercase'\|'lowercase'\|'titlecase'\|'sentencecase' | 'none' | Transformasi huruf | | applyTextCaseToValue | boolean | true | Terapkan transformasi ke v-model | | valueType | 'free'\|'number'\|'letter'\|'letternumber'\|'email'\|'password' | 'free' | Filter jenis karakter yang diperbolehkan | | allowSpaces | boolean | — | Izinkan spasi | | allowChars | string\|string[] | — | Karakter tambahan yang diizinkan | | pattern | RegExp | — | Regex validasi | | visible | boolean | true | Tampilkan/sembunyikan | | skeleton | boolean | false | Tampilkan skeleton | | returnNumber | boolean | — | Emit nilai sebagai number | | error | string\|null | — | Pesan error validasi |

<template>
  <DCodeTextField
    v-model="form.name"
    label="Nama Lengkap"
    placeholder="Masukkan nama..."
    text-case="titlecase"
    clearable
  />
  <DCodeTextField
    v-model="form.email"
    label="Email"
    value-type="email"
    :error="errors.email"
  />
  <DCodeTextField
    v-model="form.password"
    label="Password"
    value-type="password"
  />
</template>

DCodeTextarea

Textarea dengan karakter counter, filter, dan transformasi case.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | string\|number\|null | — | Value (v-model) | | clearable | boolean | — | Tombol clear | | hideClearWhenEmpty | boolean | true | Sembunyikan clear saat kosong | | textCase | 'none'\|'uppercase'\|'lowercase'\|'titlecase'\|'sentencecase' | 'none' | Transformasi huruf | | applyTextCaseToValue | boolean | true | Terapkan ke v-model | | valueType | 'free'\|'number'\|'letter'\|'letternumber' | 'free' | Filter karakter | | allowSpaces | boolean | true | Izinkan spasi | | allowChars | string\|string[] | — | Karakter tambahan | | pattern | RegExp | — | Regex validasi | | visible | boolean | true | Tampilkan/sembunyikan | | skeleton | boolean | false | Skeleton loading | | showCharCount | boolean | true | Tampilkan counter karakter | | returnNumber | boolean | false | Emit sebagai number | | error | string\|null | null | Pesan error |

<template>
  <DCodeTextarea
    v-model="form.description"
    label="Deskripsi"
    placeholder="Masukkan deskripsi..."
    :show-char-count="true"
    maxlength="500"
  />
</template>

DCodeAutoComplete

Dropdown autocomplete dengan fetch API otomatis dan pencarian.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | any | — | Value terpilih (v-model) | | endpoint | { apiUrl?, apiMethod?, apiModule?, apiAction? } | — | Konfigurasi endpoint API | | apiParams | object | {} | Parameter tambahan request | | fetchItems | Function | — | Fungsi custom fetch (alternatif endpoint) | | itemTitle | string\|Function | 'name' | Field label item | | itemValue | string\|Function | 'id' | Field nilai item | | returnObject | boolean | false | Return objek penuh | | autoFetchOnOpen | boolean | true | Fetch saat dropdown terbuka | | autoFetchOnMount | boolean | false | Fetch saat mount | | clearable | boolean | false | Tombol clear | | disabled | boolean | false | Nonaktifkan | | visible | boolean | true | Tampilkan/sembunyikan | | skeleton | boolean | false | Skeleton loading | | error | string\|null | null | Pesan error | | icon | Component | — | Ikon kiri input |

<template>
  <DCodeAutoComplete
    v-model="form.departemenId"
    label="Departemen"
    :endpoint="{ apiUrl: '/api/master/departemen' }"
    item-title="nama"
    item-value="id"
    clearable
  />
</template>

DCodeInfiniteAutoComplete

Autocomplete dengan pagination infinite scroll (load more saat scroll ke bawah).

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | any | — | Value (v-model) | | endpoint | { apiUrl?, apiMethod? } | — | Endpoint API | | apiParams | object | {} | Parameter tambahan | | fetchItems | Function | — | Custom fetch | | itemTitle | string\|Function | 'name' | Field label | | itemValue | string\|Function | 'id' | Field nilai | | itemSubtitle | string\|Function | — | Field subtitle item | | returnObject | boolean | false | Return objek penuh | | autoFetchOnOpen | boolean | true | Fetch saat buka | | refreshOnOpen | boolean | false | Refresh data setiap buka | | clearable | boolean | false | Tombol clear | | disabled | boolean | false | Nonaktifkan | | visible | boolean | true | Tampilkan | | skeleton | boolean | false | Skeleton | | error | string\|null | null | Error message | | icon | Component | — | Ikon prefix | | pageSize | number | 20 | Jumlah item per halaman |

<template>
  <DCodeInfiniteAutoComplete
    v-model="form.userId"
    label="Pilih User"
    :endpoint="{ apiUrl: '/api/users', apiMethod: 'GET' }"
    item-title="fullName"
    item-value="id"
    :page-size="15"
  />
</template>

DCodeMultiSelect

Dropdown multi-pilihan dengan chip tags dan search.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | any[] | — | Array nilai terpilih (v-model) | | items | object[] | [] | Data statis | | endpoint | { apiUrl?, apiMethod?, apiOptions? } | — | Endpoint API | | fetchItems | Function | — | Custom fetch | | autoFetchOnOpen | boolean | true | Fetch saat buka | | autoFetchOnMount | boolean | false | Fetch saat mount | | refreshOnOpen | boolean | false | Refresh setiap buka | | label | string | '' | Label dropdown | | placeholder | string | 'Pilih data…' | Placeholder | | disabled | boolean | false | Nonaktifkan | | clearable | boolean | false | Tombol clear | | searchable | boolean | false | Aktifkan search | | searchPlaceholder | string | 'Cari…' | Placeholder search | | visible | boolean | true | Tampilkan | | skeleton | boolean | false | Skeleton | | maxChips | number\|null | 2 | Maks chip ditampilkan | | maxSelected | number\|null | 3 | Maks pilihan | | itemTitle | string\|Function | — | Field label | | itemValue | string\|Function | — | Field nilai | | returnObject | boolean | — | Return objek penuh | | error | string\|null | — | Error message |

<template>
  <DCodeMultiSelect
    v-model="form.tags"
    label="Tags"
    :items="availableTags"
    item-title="name"
    item-value="id"
    :max-selected="5"
    searchable
  />
</template>

DCodeDatePicker

Date picker atau date range picker dengan berbagai format.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | string\|[string,string]\|null | — | Value tanggal (v-model) | | range | boolean | true | Mode range (pilih 2 tanggal) | | autoRangeDays | number\|null | null | Otomatis set range N hari | | disablePast | boolean | false | Nonaktifkan tanggal masa lalu | | maxDaysAhead | number\|null | null | Maks hari ke depan | | placeholder | string | 'Pilih tanggal' | Placeholder | | label | string | — | Label | | disabled | boolean | false | Nonaktifkan | | clearable | boolean | true | Tombol clear | | displayFormat | string | 'dd/MM/yyyy' | Format tampilan | | modelType | string | 'yyyy-MM-dd' | Format v-model | | locale | 'id'\|'en' | 'en' | Bahasa kalender | | placement | string | 'bottom-start' | Posisi dropdown | | prependInner | string | — | Teks prefix | | error | string\|null | null | Error message | | tone | string | 'primary' | Warna tema kalender | | skeleton | boolean | false | Skeleton loading |

<template>
  <!-- Single date -->
  <DCodeDatePicker
    v-model="form.tanggal"
    label="Tanggal Lahir"
    :range="false"
    display-format="dd MMMM yyyy"
    locale="id"
  />

  <!-- Date range -->
  <DCodeDatePicker
    v-model="form.periode"
    label="Periode"
    :range="true"
  />
</template>

DCodeCurrencyField

Input angka dengan format mata uang otomatis.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | number\|null | — | Nilai angka (v-model) | | currency | string | 'IDR' | Kode mata uang ('IDR', 'USD') | | locale | string | 'en-US' | Locale format angka | | label | string | — | Label field | | placeholder | string | — | Placeholder | | error | string\|null | — | Error message | | disabled | boolean | — | Nonaktifkan | | readonly | boolean | — | Read-only | | clearable | boolean | — | Tombol clear | | hideClearWhenEmpty | boolean | true | Sembunyikan clear jika kosong | | prependInner | string | — | Override simbol mata uang | | skeleton | boolean | false | Skeleton loading |

<template>
  <DCodeCurrencyField
    v-model="form.harga"
    label="Harga"
    currency="IDR"
    clearable
    :error="errors.harga"
  />
</template>

DCodeCheckbox

Checkbox dengan label dan dukungan indeterminate state.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | boolean\|null | — | Nilai checkbox (v-model) | | visible | boolean | true | Tampilkan | | skeleton | boolean | false | Skeleton | | indeterminate | boolean | false | State indeterminate | | disabled | boolean | false | Nonaktifkan |

Gunakan attr label untuk teks di samping checkbox.

<template>
  <DCodeCheckbox
    v-model="form.setuju"
    label="Saya setuju dengan syarat & ketentuan"
    required
  />
</template>

DCodeSwitch

Toggle switch dengan berbagai ukuran, warna, dan tooltip.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | boolean | false | Status switch (v-model) | | label | string | '' | Label teks | | disabled | boolean | false | Nonaktifkan | | size | 'small'\|'medium'\|'large' | 'medium' | Ukuran switch | | color | 'primary'\|'success'\|'warning'\|'danger' | 'primary' | Warna aktif | | loading | boolean | false | Status loading | | labelPosition | 'left'\|'right' | 'right' | Posisi label | | required | boolean | false | Required | | showTooltip | boolean | false | Tampilkan tooltip | | tooltipActiveText | string | 'Aktif' | Teks tooltip saat aktif | | tooltipInactiveText | string | 'Tidak Aktif' | Teks tooltip saat nonaktif |

<template>
  <DCodeSwitch
    v-model="form.isActive"
    label="Status Aktif"
    color="success"
    size="medium"
    :show-tooltip="true"
  />
</template>

DCodeRadio

Grup radio button yang bisa fetch opsi dari API.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | any | — | Value terpilih (v-model) | | label | string | 'name' | Key field label dari opsi | | options | any[] | [] | Opsi data statis | | apiUrl | string | '' | URL endpoint API untuk fetch opsi | | apiMethod | 'GET'\|'POST' | 'GET' | HTTP method | | dataPath | string | '' | Path ke array data dalam response | | layout | boolean | true | Layout horizontal (true) / vertikal | | disabled | boolean | false | Nonaktifkan | | required | boolean | false | Required | | error | string\|null | null | Error message | | size | 'sm'\|'md'\|'lg' | 'md' | Ukuran radio | | color | string | 'blue' | Warna accent | | model | string | 'id' | Key field nilai dari opsi |

<template>
  <!-- Dengan opsi statis -->
  <DCodeRadio
    v-model="form.gender"
    :options="[
      { id: 'L', name: 'Laki-laki' },
      { id: 'P', name: 'Perempuan' },
    ]"
  />

  <!-- Dengan API -->
  <DCodeRadio
    v-model="form.statusId"
    api-url="/api/master/status"
    label="nama"
    model="id"
  />
</template>

DCodeRadioCustom

Radio button dengan tampilan card kustom dan ikon.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | any | — | Value (v-model) | | icon | string | — | Nama ikon Lucide untuk setiap card | | label | string | 'name' | Key field label | | options | any[] | [] | Opsi statis | | apiUrl | string | '' | URL API | | apiMethod | 'GET'\|'POST' | 'GET' | HTTP method | | dataPath | string | '' | Path data dalam response | | layout | boolean | true | Horizontal layout | | disabled | boolean | false | Nonaktifkan | | required | boolean | false | Required | | error | string\|null | null | Error message | | size | 'sm'\|'md'\|'lg' | 'md' | Ukuran | | color | string | 'blue' | Warna highlight | | model | string | 'id' | Key field nilai |

<template>
  <DCodeRadioCustom
    v-model="form.plan"
    :options="[
      { id: 'basic', name: 'Basic', icon: 'Star' },
      { id: 'pro', name: 'Pro', icon: 'Zap' },
    ]"
    label="name"
    model="id"
  />
</template>

DCodeOtpInput

Input OTP digit-per-digit dengan submit otomatis ke API.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | string | '' | Nilai OTP (v-model) | | totalInput | number | 6 | Jumlah digit OTP | | default | string | '' | Default nilai awal | | label | string | '' | Label | | endpoint | { apiMethod?, apiUrl? } | — | Endpoint untuk submit OTP | | submitFn | Function | — | Custom submit function |

Events: complete (OTP penuh), success, error, loading

<template>
  <DCodeOtpInput
    v-model="otp"
    :total-input="6"
    label="Kode OTP"
    :endpoint="{ apiUrl: '/api/auth/verify-otp', apiMethod: 'POST' }"
    @complete="handleOtpComplete"
  />
</template>

DCodeFileField

Upload file dengan validasi tipe & ukuran, preview, dan upload ke API.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | File\|null | — | File terpilih (v-model) | | label | string | 'Upload File' | Label | | hint | string | — | Teks petunjuk | | disabled | boolean | false | Nonaktifkan | | previewHeight | number | 190 | Tinggi preview (px) | | previewWidth | number | — | Lebar preview (px) | | allowedTypes | string\|null | null | Tipe file diizinkan (e.g. 'pdf,doc') | | accept | string | — | Attribute accept HTML | | minSizeMb | number\|null | null | Ukuran file minimum (MB) | | maxSizeMb | number | 5 | Ukuran file maksimum (MB) | | endpoint | { apiMethod?, apiUrl? } | — | Endpoint upload | | uploadFn | Function | — | Custom upload function |

<template>
  <DCodeFileField
    v-model="form.dokumen"
    label="Upload Dokumen"
    allowed-types="pdf,doc,docx"
    :max-size-mb="10"
    :endpoint="{ apiUrl: '/api/upload', apiMethod: 'POST' }"
    @uploaded="handleUploaded"
    @error="handleError"
  />
</template>

DCodeImageField

Upload gambar dengan crop/resize dialog terintegrasi.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | modelValue | File\|null | — | File gambar (v-model) | | label | string | 'Upload Gambar' | Label | | hint | string | 'Drag & drop atau klik area ini' | Petunjuk | | disabled | boolean | false | Nonaktifkan | | allowedTypes | string\|null | null | Tipe gambar diizinkan | | accept | string | 'image/*' | Accept HTML | | minSizeMb | number\|null | null | Min ukuran (MB) | | maxSizeMb | number | 2 | Maks ukuran (MB) | | title | string | 'Hasil Gambar' | Judul preview | | description | string | '' | Deskripsi preview | | previewWidth | number | 360 | Lebar preview | | previewHeight | number | 220 | Tinggi preview | | aspectRatio | number\|null | null | Rasio aspek crop | | outputWidth | number\|null | null | Lebar output (px) | | outputHeight | number\|null | null | Tinggi output (px) | | quality | number | 0.92 | Kualitas output (0-1) | | autoOpenEditor | boolean | true | Buka editor saat file dipilih | | minWidth | number\|null | null | Min lebar gambar | | minHeight | number\|null | null | Min tinggi gambar | | maxWidth | number\|null | null | Maks lebar gambar | | maxHeight | number\|null | null | Maks tinggi gambar | | endpoint | { apiMethod?, apiUrl? } | — | Endpoint upload | | uploadFn | Function | — | Custom upload | | editUseOriginal | boolean | false | Edit dari foto asli (bukan ter-crop) |

<template>
  <DCodeImageField
    v-model="form.foto"
    label="Foto Profil"
    :aspect-ratio="1"
    :output-width="300"
    :output-height="300"
    :max-size-mb="2"
  />
</template>

DCodeDropzone

Area drag-and-drop untuk upload file.

| Prop | Tipe | Default | Deskripsi | |------|------|---------|-----------| | label | string | 'Upload File' | Label | | hint | string | 'Drag & drop file di sini atau klik area ini' | Petunjuk | | accept | string | 'image/*' | Tipe file diterima | | maxSizeMb | number | 2 | Maks ukuran (MB) | | mode |