@stsdti/funky-ui-kit 0.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 ADDED
@@ -0,0 +1,573 @@
1
+ <p align="center">
2
+ <img src="https://img.shields.io/badge/vue-3.x-42b883?style=for-the-badge&logo=vuedotjs&logoColor=white" alt="Vue 3" />
3
+ <img src="https://img.shields.io/badge/vite-6.x-646cff?style=for-the-badge&logo=vite&logoColor=white" alt="Vite" />
4
+ <img src="https://img.shields.io/badge/version-1.16.0-blue?style=for-the-badge" alt="Version" />
5
+ <img src="https://img.shields.io/badge/license-proprietary-red?style=for-the-badge" alt="License" />
6
+ </p>
7
+
8
+ <h1 align="center">๐ŸŽจ Funky UI Kit</h1>
9
+
10
+ <p align="center">
11
+ <strong>A comprehensive, opinionated Vue 3 component library packed with 70+ ready-to-use components, powerful composables, and utility functions โ€” designed to accelerate enterprise Vue development.</strong>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <code>@stsdti/funky-ui-kit</code>
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## โœจ Highlights
21
+
22
+ - ๐Ÿงฉ **70+ Components** โ€” From basic inputs to complex data tables, form wizards, and file previews
23
+ - ๐Ÿช **Powerful Composables** โ€” `useForm`, `useTable`, `useSelect`, and more for rapid feature development
24
+ - ๐Ÿ—๏ธ **Repository Pattern** โ€” Built-in `BaseRepository` with filtering, sorting, pagination, and request deduplication
25
+ - ๐ŸŽฏ **Form Binding System** โ€” Reactive `getBinding()` API that wires up `v-model`, validation, and field state in one call
26
+ - ๐Ÿ”Œ **Plugin Architecture** โ€” Single `app.use()` call registers all components, directives, and integrations
27
+ - ๐Ÿ“ฆ **Tree-Shakable** โ€” ESM output with code-split CSS for optimized bundle sizes
28
+ - ๐ŸŽจ **Limitless Theme** โ€” Built-in SCSS-based design system with a rich color palette and icon set
29
+ - ๐Ÿงช **TypeScript Support** โ€” Ships with generated type declarations and JetBrains `web-types.json`
30
+
31
+ ---
32
+
33
+ ## ๐Ÿ“ฆ Installation
34
+
35
+ ```bash
36
+ npm install @stsdti/funky-ui-kit
37
+ ```
38
+
39
+ ### Peer Dependencies
40
+
41
+ Make sure the following are installed in your host project:
42
+
43
+ ```bash
44
+ npm install vue@^3.5 vue-router@^4.2 axios@^1.6
45
+ ```
46
+
47
+ ---
48
+
49
+ ## ๐Ÿš€ Quick Start
50
+
51
+ ### 1. Register the Plugin
52
+
53
+ ```js
54
+ // main.js
55
+ import { createApp } from 'vue'
56
+ import FunkyUIKit from '@stsdti/funky-ui-kit'
57
+ import '@stsdti/funky-ui-kit/dist/funky-ui-kit.css'
58
+
59
+ const app = createApp(App)
60
+
61
+ app.use(FunkyUIKit, {
62
+ router, // vue-router instance โ€” enables route-aware features
63
+ mediaType, // optional โ€” configures media handling strategy
64
+ })
65
+
66
+ app.mount('#app')
67
+ ```
68
+
69
+ > **That's it!** All 70+ components are now globally available. No individual imports needed.
70
+
71
+ ### 2. Use Components
72
+
73
+ ```vue
74
+ <template>
75
+ <AppCard>
76
+ <AppInput v-model="name" label="Full Name" />
77
+ <AppSelect v-model="role" :fetch="fetchRoles" />
78
+ <AppButton @click="submit">Save</AppButton>
79
+ </AppCard>
80
+ </template>
81
+ ```
82
+
83
+ ### 3. Import Composables & Utilities
84
+
85
+ ```js
86
+ import {
87
+ useForm,
88
+ useTable,
89
+ useSelect,
90
+ useFormValidation,
91
+ useTableFilters,
92
+ BaseRepository,
93
+ DateUtils,
94
+ UIUtils,
95
+ ResponseUtils,
96
+ } from '@stsdti/funky-ui-kit'
97
+ ```
98
+
99
+ ---
100
+
101
+ ## ๐Ÿงฉ Component Catalog
102
+
103
+ ### Layout Components
104
+
105
+ | Component | Description |
106
+ |-----------|-------------|
107
+ | `AppSidebar` | Application sidebar with collapsible navigation |
108
+ | `AppSidebarHeader` | Header section for the sidebar |
109
+ | `AppToolbar` | Top toolbar / app bar |
110
+ | `AppFooter` | Application footer |
111
+
112
+ ### Form Inputs
113
+
114
+ | Component | Description |
115
+ |-----------|-------------|
116
+ | `AppInput` | Text input with label, validation, and form element integration |
117
+ | `AppNumberInput` | Numeric input with increment/decrement controls |
118
+ | `AppTextArea` | Multi-line text input |
119
+ | `AppCheckbox` | Checkbox with label support |
120
+ | `AppRadioButton` | Radio button group |
121
+ | `AppToggleButton` | Toggle / switch control |
122
+ | `AppSliderInput` | Range slider input |
123
+ | `AppColorPicker` | Color picker with palette support |
124
+ | `AppFormElement` | Base wrapper that provides label, validation state, and layout to any input |
125
+
126
+ ### Select & Pickers
127
+
128
+ | Component | Description |
129
+ |-----------|-------------|
130
+ | `AppSelect` | Feature-rich dropdown select with search, async fetching, and multi-select |
131
+ | `AppSelectNew` | Next-generation select component |
132
+ | `AppSelectBoolean` | Boolean yes/no select |
133
+ | `AppSelectIcon` | Icon picker select |
134
+ | `AppDatePicker` | Date picker (powered by `@vuepic/vue-datepicker`) |
135
+ | `AppDatePickerRange` | Date range picker |
136
+ | `AppDatePickerDayOfWeek` | Day-of-week picker |
137
+ | `AppDateTimePicker` | Combined date + time picker |
138
+ | `AppTimePicker` | Standalone time picker |
139
+ | `AppMonthPicker` | Month-only picker |
140
+ | `AppMonthYearPicker` | Month + year picker |
141
+ | `AppYearPicker` | Year-only picker |
142
+ | `AppGridPicker` | Grid-based option picker |
143
+
144
+ ### Data Display
145
+
146
+ | Component | Description |
147
+ |-----------|-------------|
148
+ | `AppTable` | Data table with sorting, pagination, and filtering |
149
+ | `AppTableMagic` | Enhanced table with advanced features |
150
+ | `AppTableFilter` | Filter panel for tables |
151
+ | `AppTableFilterDrawer` | Side-drawer variant of table filters |
152
+ | `AppPagination` | Standalone pagination control |
153
+ | `AppBadge` | Status badge / label |
154
+ | `AppTag` | Colored tag chip |
155
+ | `AppRemovableTag` | Tag with remove action |
156
+ | `AppProgressBar` | Determinate progress bar |
157
+ | `AppProgressBarOverlay` | Full-screen progress overlay |
158
+ | `AppSummary` | Summary info display |
159
+ | `AppEllipsisText` | Text with overflow ellipsis and tooltip |
160
+ | `AppHtml` | Sanitized HTML renderer (powered by DOMPurify) |
161
+ | `AppTree` | Hierarchical tree view |
162
+
163
+ ### Navigation
164
+
165
+ | Component | Description |
166
+ |-----------|-------------|
167
+ | `AppBreadcrumbs` | Breadcrumb trail |
168
+ | `AppTabs` | Tab navigation |
169
+ | `AppTabsBoolean` | Two-state tab toggle |
170
+ | `AppLink` | Enhanced router link |
171
+ | `AppMenu` | Dropdown menu |
172
+ | `AppScrollToTop` | Scroll-to-top button |
173
+ | `AppScrollToBottom` | Scroll-to-bottom button |
174
+ | `AppScrollTopAndBottom` | Combined scroll controls |
175
+ | `AppToolbarSubheader` | Secondary toolbar header |
176
+
177
+ ### Buttons
178
+
179
+ | Component | Description |
180
+ |-----------|-------------|
181
+ | `AppButton` | Primary button with variants and icons |
182
+ | `AppButtonGroup` | Grouped button set |
183
+
184
+ ### Feedback & Overlays
185
+
186
+ | Component | Description |
187
+ |-----------|-------------|
188
+ | `AppModal` | Modal dialog |
189
+ | `AppModalCard` | Card-style modal |
190
+ | `AppFormModal` | Modal with built-in form handling |
191
+ | `AppPreviewModal` | Preview / detail modal |
192
+ | `AppLoadingModal` | Loading state modal |
193
+ | `AppDrawer` | Slide-out drawer panel |
194
+ | `AppOverlay` | Background overlay |
195
+ | `AppLoadingOverlay` | Full-screen loading overlay |
196
+ | `AppLoading` | Inline loading spinner |
197
+ | `AppDirtyToast` | Unsaved changes notification |
198
+
199
+ ### Files & Media
200
+
201
+ | Component | Description |
202
+ |-----------|-------------|
203
+ | `AppFile` | File display component |
204
+ | `AppFileNew` | Next-generation file component |
205
+ | `AppFileInput` | File upload input |
206
+ | `AppFileUpload` | Drag-and-drop file upload |
207
+ | `AppFilePreview` | File preview (supports PDF via `pdfjs-dist` and DOCX via `docx-preview`) |
208
+ | `AppIframe` | Embedded iframe viewer |
209
+ | `AppCarousel` | Image/content carousel |
210
+
211
+ ### Layout & Structure
212
+
213
+ | Component | Description |
214
+ |-----------|-------------|
215
+ | `AppCard` | Content card container |
216
+ | `AppPage` | Page-level layout wrapper |
217
+ | `AppChild` | Router child view wrapper |
218
+ | `AppIcon` | Icon component (powered by `@tabler/icons-vue`) |
219
+ | `AppDualListMagic` | Dual-list transfer component |
220
+ | `AppFormRepeater` | Dynamic form field repeater |
221
+ | `AppFormWizard` | Multi-step form wizard |
222
+ | `AppTeleport` | Vue Teleport wrapper |
223
+ | `AppTeleportTarget` | Teleport mount target |
224
+
225
+ ### Skeleton & Placeholders
226
+
227
+ | Component | Description |
228
+ |-----------|-------------|
229
+ | `AppSkeleton` | Loading skeleton element |
230
+ | `AppSkeletonGroup` | Grouped skeleton layout |
231
+ | `AppPlaceholderCard` | Card placeholder |
232
+ | `AppPlaceholderHeading` | Heading placeholder |
233
+ | `AppPlaceholderImage` | Image placeholder |
234
+ | `AppPlaceholderText` | Text placeholder |
235
+ | `AppPlaceholderTitle` | Title placeholder |
236
+
237
+ ---
238
+
239
+ ## ๐Ÿช Composables
240
+
241
+ ### `useForm` โ€” Full-Featured Form Management
242
+
243
+ The crown jewel of Funky UI Kit. Manages the entire lifecycle of a form: fetching, binding, validation, dirty tracking, saving, and deleting.
244
+
245
+ ```js
246
+ import { useForm } from '@stsdti/funky-ui-kit'
247
+
248
+ const form = useForm({
249
+ model: MyModel, // model with repository + transformer
250
+ useDirty: true, // track unsaved changes
251
+ onEvent: (event, ctx) => {
252
+ // 'mounted' | 'inited' | 'fetched'
253
+ },
254
+ }, emit)
255
+
256
+ // Reactive field binding โ€” wires up v-model + validation in one call
257
+ const nameBinding = form.getBinding('name')
258
+ const emailBinding = form.getBinding('email')
259
+ ```
260
+
261
+ ```vue
262
+ <template>
263
+ <AppInput v-bind="nameBinding" label="Name" />
264
+ <AppInput v-bind="emailBinding" label="Email" />
265
+ <AppButton @click="form.onSave()">Save</AppButton>
266
+ </template>
267
+ ```
268
+
269
+ **Returned API:**
270
+
271
+ | Property / Method | Description |
272
+ |---|---|
273
+ | `item` | Reactive ref containing the form data |
274
+ | `isLoading` | Loading state ref |
275
+ | `isDirty` | Computed โ€” `true` if form has unsaved changes |
276
+ | `getBinding(path)` | Creates a reactive binding object for a field path |
277
+ | `getBindingMedia(path, tag)` | Creates media-specific bindings with tag filtering |
278
+ | `initItem({ id, value })` | Initialize or reset the form item |
279
+ | `onSave()` | Validate โ†’ transform โ†’ save (insert or update) |
280
+ | `onDelete()` | Show confirmation โ†’ delete โ†’ navigate |
281
+ | `refreshItem()` | Re-fetch the current item |
282
+ | `setPayloadIsDisabled(key)` | Programmatically disable fields |
283
+ | `setPayloadIsHidden(key)` | Programmatically hide fields |
284
+
285
+ ---
286
+
287
+ ### `useTable` โ€” Server-Side Table Management
288
+
289
+ Manages paginated, sortable, filterable tables with full URL-based filter persistence.
290
+
291
+ ```js
292
+ import { useTable } from '@stsdti/funky-ui-kit'
293
+
294
+ const table = useTable({
295
+ fetchOnMount: true,
296
+ getTable: async (request) => {
297
+ return await myRepository.fetchTable(request)
298
+ },
299
+ tableFilters: {
300
+ filters: { search: '', status: null },
301
+ filterBuilders: {
302
+ search: (value) => ({ field: 'search', value }),
303
+ status: (value) => ({ field: 'status', value }),
304
+ },
305
+ },
306
+ }, emit)
307
+ ```
308
+
309
+ **Returned API:**
310
+
311
+ | Property / Method | Description |
312
+ |---|---|
313
+ | `tableData` | Reactive table response (rows, pagination meta) |
314
+ | `tableRows` | Computed array of current rows |
315
+ | `isLoading` | Loading state |
316
+ | `onFilterSet()` | Apply current filters and refetch |
317
+ | `onFilterReset()` | Reset all filters to defaults |
318
+ | `onTablePageChange(page)` | Navigate to a specific page |
319
+ | `onTableSortChange(sort)` | Apply column sorting |
320
+ | `fetchTable()` | Manually trigger a table fetch |
321
+
322
+ ---
323
+
324
+ ### `useSelect` โ€” Async Select Options
325
+
326
+ Manages option fetching, search, debouncing, and auto-selection for select components.
327
+
328
+ ```js
329
+ import { useSelect } from '@stsdti/funky-ui-kit'
330
+
331
+ const { options, isLoading, fetchOptions, onSearch } = useSelect({
332
+ modelValue: selectedRef,
333
+ fetch: async (search) => api.searchUsers(search),
334
+ shouldFetchOnMount: ref(true),
335
+ }, emit)
336
+ ```
337
+
338
+ ---
339
+
340
+ ### Other Composables
341
+
342
+ | Composable | Description |
343
+ |---|---|
344
+ | `useFormValidation` | Yup-based validation engine used internally by `useForm` |
345
+ | `useFormDirty` | Dirty-state tracking with toast notifications |
346
+ | `useTableFilters` | Filter builder with URL persistence and backend filter generation |
347
+ | `useModelWatch` | Deep watcher for model changes with debounce |
348
+ | `useAppFormElement` | Shared form element logic (labels, errors, required state) |
349
+ | `useAdaptiveDropdown` | Dropdown positioning that adapts to viewport boundaries |
350
+ | `useTeleport` | Helper for teleporting content to specific DOM targets |
351
+ | `useScrollToError` | Auto-scroll to the first validation error in a form |
352
+
353
+ ---
354
+
355
+ ## ๐Ÿ—๏ธ Architecture
356
+
357
+ ### Repository Pattern
358
+
359
+ `BaseRepository` provides a complete data-access layer with built-in request deduplication:
360
+
361
+ ```js
362
+ import { BaseRepository } from '@stsdti/funky-ui-kit'
363
+ import axios from 'axios'
364
+
365
+ class UserRepository extends BaseRepository {
366
+ constructor() {
367
+ super('/api/users', axios)
368
+ }
369
+ }
370
+
371
+ const repo = new UserRepository()
372
+
373
+ // CRUD
374
+ const user = await repo.fetchOne(1)
375
+ const users = await repo.fetchAll({ filters: [{ field: 'role', value: 'admin' }] })
376
+ await repo.insert({ name: 'John' })
377
+ await repo.update(1, { name: 'Jane' })
378
+ await repo.delete(1)
379
+
380
+ // Table integration
381
+ const tableData = await repo.fetchTable(tableRequest)
382
+
383
+ // Fetch all pages automatically
384
+ const allRecords = await repo.fetchAllWithMerge({ filters: [...], limit: 100 })
385
+ ```
386
+
387
+ ### Transformer Pattern
388
+
389
+ Transform data between API and frontend representations:
390
+
391
+ ```js
392
+ class UserTransformer extends Transformer {
393
+ transformFromApi(data) {
394
+ return { ...data, fullName: `${data.first_name} ${data.last_name}` }
395
+ }
396
+
397
+ transformToApi(data) {
398
+ const { fullName, ...rest } = data
399
+ return rest
400
+ }
401
+ }
402
+ ```
403
+
404
+ ### Model Pattern
405
+
406
+ Models tie together repositories, transformers, empty states, and routes:
407
+
408
+ ```js
409
+ class UserModel extends BaseModel {
410
+ getRepository() { return new UserRepository() }
411
+ getTransformer() { return new UserTransformer() }
412
+ getEmpty() { return { name: '', email: '', role: null } }
413
+ getRouteForm() { return '/users/:id' }
414
+ getRouteList() { return '/users' }
415
+ }
416
+ ```
417
+
418
+ ---
419
+
420
+ ## ๐Ÿ› ๏ธ Utilities
421
+
422
+ ### DateUtils
423
+ Date formatting and manipulation powered by `date-fns`.
424
+
425
+ ### UIUtils
426
+ UI helpers including toast notifications (`showToastSuccess`, `showToastError`) and SweetAlert2 confirmations (`showDeleteConfirmation`).
427
+
428
+ ### ResponseUtils
429
+ Response status checking and error extraction helpers.
430
+
431
+ ### VueUtils
432
+ Advanced Vue reactivity utilities:
433
+
434
+ | Function | Description |
435
+ |---|---|
436
+ | `getFormBuilder(veeForm)` | Create form bindings from a VeeValidate form instance |
437
+ | `getBinding(veeForm, path)` | Generate reactive field binding with validation |
438
+ | `bindOneWay(source, dest)` | One-directional reactive binding between refs |
439
+ | `bindTwoWay(source, dest)` | Bi-directional reactive binding between refs |
440
+ | `generateComputedChildren(ref, keys)` | Create computed properties for nested object keys |
441
+ | `getBindingImmutableFor(modelValue)` | Immutable binding factory for deeply nested forms |
442
+ | `flattenSchemaFields(fields)` | Flatten nested Yup schema for field enumeration |
443
+
444
+ ### Other Utilities
445
+
446
+ | Module | Description |
447
+ |---|---|
448
+ | `AccentUtils` | Diacritics-aware string normalization |
449
+ | `DataUtils` | General data manipulation helpers |
450
+ | `TableUtils` | Table request/response factories |
451
+ | `UrlUtils` | URL manipulation utilities |
452
+ | `BackgroundColorUtils` | Dynamic background color calculations |
453
+ | `BreadcrumbUtils` | Breadcrumb path generation |
454
+
455
+ ---
456
+
457
+ ## ๐Ÿ“‹ Directives
458
+
459
+ ### `v-horizontal-scroll`
460
+
461
+ Enables horizontal scrolling via mouse wheel on any container element.
462
+
463
+ ```vue
464
+ <div v-horizontal-scroll>
465
+ <!-- horizontally overflowing content -->
466
+ </div>
467
+ ```
468
+
469
+ ---
470
+
471
+ ## โš™๏ธ Configuration
472
+
473
+ ### Colors
474
+ A comprehensive color palette available via `Colors.js` โ€” provides a curated set of named colors for consistent theming across the application.
475
+
476
+ ### IconConstants
477
+ A centralized icon mapping dictionary providing consistent icon usage throughout the application, backed by `@tabler/icons-vue`.
478
+
479
+ ### Constants
480
+ Application-wide constants including default pagination values and shared enumerations.
481
+
482
+ ### APIConstants
483
+ HTTP status code helpers and response validation utilities.
484
+
485
+ ---
486
+
487
+ ## ๐Ÿ”ง Development
488
+
489
+ ### Prerequisites
490
+
491
+ - Node.js 18+
492
+ - npm 9+
493
+
494
+ ### Setup
495
+
496
+ ```bash
497
+ git clone <repository-url>
498
+ cd funky-ui-kit
499
+ npm install
500
+ ```
501
+
502
+ ### Available Scripts
503
+
504
+ | Command | Description |
505
+ |---------|-------------|
506
+ | `npm run dev` | Start the Vite dev server with the demo app |
507
+ | `npm run build` | Build the library for production |
508
+ | `npm run build:watch` | Build in watch mode for development |
509
+ | `npm run build:package` | Build in package mode |
510
+ | `npm run post-processing` | Generate composable re-exports and type declarations |
511
+ | `npm run lint` | Lint and auto-fix with ESLint |
512
+ | `npm run format` | Format code with Prettier |
513
+
514
+ ### Project Structure
515
+
516
+ ```
517
+ funky-ui-kit/
518
+ โ”œโ”€โ”€ src/
519
+ โ”‚ โ”œโ”€โ”€ assets/ # Theme files, fonts, icons (Limitless theme)
520
+ โ”‚ โ”œโ”€โ”€ components/
521
+ โ”‚ โ”‚ โ”œโ”€โ”€ big/ # Layout components (Sidebar, Toolbar, Footer)
522
+ โ”‚ โ”‚ โ””โ”€โ”€ small/ # All feature components (70+ components)
523
+ โ”‚ โ”œโ”€โ”€ composables/ # Vue composables (useForm, useTable, useSelect, โ€ฆ)
524
+ โ”‚ โ”œโ”€โ”€ config/ # Constants, colors, icons, API config
525
+ โ”‚ โ”œโ”€โ”€ directives/ # Custom Vue directives
526
+ โ”‚ โ”œโ”€โ”€ mixins/ # Shared prop definitions
527
+ โ”‚ โ”œโ”€โ”€ models/ # BaseModel, Media model
528
+ โ”‚ โ”œโ”€โ”€ repositories/ # BaseRepository
529
+ โ”‚ โ”œโ”€โ”€ transformers/ # Transformer, ApiTransformer
530
+ โ”‚ โ”œโ”€โ”€ utils/ # Utility modules
531
+ โ”‚ โ”œโ”€โ”€ components.js # Component registry
532
+ โ”‚ โ”œโ”€โ”€ index.js # Library entry point & exports
533
+ โ”‚ โ””โ”€โ”€ plugin.js # Vue plugin core (router, media config)
534
+ โ”œโ”€โ”€ demo/ # Demo/playground app
535
+ โ”œโ”€โ”€ scripts/ # Build post-processing scripts
536
+ โ”œโ”€โ”€ dist/ # Built output
537
+ โ”œโ”€โ”€ web-types.json # JetBrains IDE autocomplete support
538
+ โ””โ”€โ”€ package.json
539
+ ```
540
+
541
+ ---
542
+
543
+ ## ๐Ÿค IDE Support
544
+
545
+ Funky UI Kit ships with `web-types.json` for **JetBrains IDEs** (WebStorm, IntelliJ) โ€” providing full component autocompletion, prop hints, and documentation inline.
546
+
547
+ For **VS Code**, install the [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) extension for optimal Vue 3 + TypeScript support.
548
+
549
+ ---
550
+
551
+ ## ๐Ÿ“„ Key Dependencies
552
+
553
+ | Package | Purpose |
554
+ |---------|---------|
555
+ | `@tabler/icons-vue` | Icon library |
556
+ | `@vuepic/vue-datepicker` | Date picker engine |
557
+ | `@vueuse/core` | Vue composition utilities |
558
+ | `date-fns` | Date manipulation |
559
+ | `dompurify` | HTML sanitization |
560
+ | `gsap` | Animations |
561
+ | `pdfjs-dist` | PDF rendering |
562
+ | `docx-preview` | DOCX file preview |
563
+ | `sweetalert2` | Alert/confirmation dialogs |
564
+ | `vue-tippy` | Tooltip directive |
565
+ | `vue-toast-notification` | Toast notifications |
566
+ | `vuedraggable` | Drag-and-drop lists |
567
+ | `viewerjs` | Image viewer |
568
+
569
+ ---
570
+
571
+ <p align="center">
572
+ <sub>Built with โค๏ธ by <strong>STS โ€” DTI</strong></sub>
573
+ </p>