@htlkg/components 0.0.2 → 0.0.4

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.
Files changed (60) hide show
  1. package/README.md +52 -0
  2. package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js +367 -0
  3. package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js.map +1 -0
  4. package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js +263 -0
  5. package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js.map +1 -0
  6. package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js +580 -0
  7. package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js.map +1 -0
  8. package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js +187 -0
  9. package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js.map +1 -0
  10. package/dist/_plugin-vue_export-helper-1tPrXgE0.js +11 -0
  11. package/dist/_plugin-vue_export-helper-1tPrXgE0.js.map +1 -0
  12. package/dist/components.css +15 -0
  13. package/dist/composables/index.js +32 -573
  14. package/dist/composables/index.js.map +1 -1
  15. package/dist/data/index.js +18 -0
  16. package/dist/data/index.js.map +1 -0
  17. package/dist/domain/index.js +8 -0
  18. package/dist/domain/index.js.map +1 -0
  19. package/dist/filterHelpers-DgRyoYSa.js +1386 -0
  20. package/dist/filterHelpers-DgRyoYSa.js.map +1 -0
  21. package/dist/forms/index.js +6 -0
  22. package/dist/forms/index.js.map +1 -0
  23. package/dist/index-DGO_pNgG.js +79 -0
  24. package/dist/index-DGO_pNgG.js.map +1 -0
  25. package/dist/index-QK97OdqQ.js +25 -0
  26. package/dist/index-QK97OdqQ.js.map +1 -0
  27. package/dist/index.js +67 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/navigation/index.js +8 -0
  30. package/dist/navigation/index.js.map +1 -0
  31. package/dist/overlays/index.js +8 -0
  32. package/dist/overlays/index.js.map +1 -0
  33. package/dist/stores/index.js +14 -0
  34. package/dist/stores/index.js.map +1 -0
  35. package/dist/useAdminPage-GhgXp0x8.js +1070 -0
  36. package/dist/useAdminPage-GhgXp0x8.js.map +1 -0
  37. package/dist/useTable-DutR1gkg.js +293 -0
  38. package/dist/useTable-DutR1gkg.js.map +1 -0
  39. package/package.json +43 -14
  40. package/src/composables/composables.md +109 -0
  41. package/src/composables/index.ts +69 -0
  42. package/src/composables/useAdminPage.ts +462 -0
  43. package/src/composables/useConfirmation.ts +358 -0
  44. package/src/composables/usePageContext.ts +171 -0
  45. package/src/composables/useStats.ts +361 -0
  46. package/src/composables/useTable.ts +26 -5
  47. package/src/composables/useWizard.ts +448 -0
  48. package/src/data/DataTable.vue +553 -0
  49. package/src/data/Table/Table.vue +295 -0
  50. package/src/data/columnHelpers.ts +503 -0
  51. package/src/data/data.md +106 -0
  52. package/src/data/filterHelpers.ts +358 -0
  53. package/src/data/index.ts +31 -0
  54. package/src/domain/domain.md +102 -0
  55. package/src/forms/JsonSchemaForm.vue +4 -1
  56. package/src/forms/forms.md +89 -0
  57. package/src/index.ts +4 -3
  58. package/src/navigation/navigation.md +80 -0
  59. package/src/overlays/overlays.md +86 -0
  60. package/src/stores/stores.md +82 -0
@@ -0,0 +1,503 @@
1
+ /**
2
+ * Column Definition Helpers
3
+ *
4
+ * Pre-built column definitions for common patterns, reducing boilerplate
5
+ * when defining DataTable columns.
6
+ */
7
+
8
+ import type { DataTableColumn } from "./DataTable.vue";
9
+
10
+ /**
11
+ * Date formatting options
12
+ */
13
+ export interface DateFormatOptions {
14
+ format?: "short" | "medium" | "long" | "full";
15
+ locale?: string;
16
+ }
17
+
18
+ /**
19
+ * Tag color function type
20
+ */
21
+ export type TagColorFn<T> = (item: T) => string;
22
+
23
+ /**
24
+ * Pre-built column helpers
25
+ */
26
+ export const columns = {
27
+ /**
28
+ * Simple text column
29
+ *
30
+ * @example
31
+ * columns.text('name', 'Name')
32
+ */
33
+ text: <T = any>(key: string, label: string, options?: { sortable?: boolean; width?: string }): DataTableColumn<T> => ({
34
+ key,
35
+ label,
36
+ type: "text",
37
+ sortable: options?.sortable ?? true,
38
+ width: options?.width,
39
+ }),
40
+
41
+ /**
42
+ * Number column with locale formatting
43
+ *
44
+ * @example
45
+ * columns.number('amount', 'Amount')
46
+ * columns.number('price', 'Price', { prefix: '$', decimals: 2 })
47
+ */
48
+ number: <T = any>(
49
+ key: string,
50
+ label: string,
51
+ options?: { prefix?: string; suffix?: string; decimals?: number; sortable?: boolean }
52
+ ): DataTableColumn<T> => ({
53
+ key,
54
+ label,
55
+ type: "number",
56
+ sortable: options?.sortable ?? true,
57
+ render: (item: any) => {
58
+ const value = item[key];
59
+ if (value === null || value === undefined) return "";
60
+
61
+ const formatted =
62
+ options?.decimals !== undefined
63
+ ? Number(value).toLocaleString(undefined, {
64
+ minimumFractionDigits: options.decimals,
65
+ maximumFractionDigits: options.decimals,
66
+ })
67
+ : Number(value).toLocaleString();
68
+
69
+ return `${options?.prefix ?? ""}${formatted}${options?.suffix ?? ""}`;
70
+ },
71
+ }),
72
+
73
+ /**
74
+ * Date column with formatting
75
+ *
76
+ * @example
77
+ * columns.date('createdAt', 'Created')
78
+ * columns.date('updatedAt', 'Updated', { format: 'long' })
79
+ */
80
+ date: <T = any>(key: string, label: string, options?: DateFormatOptions & { sortable?: boolean }): DataTableColumn<T> => ({
81
+ key,
82
+ label,
83
+ type: "date",
84
+ sortable: options?.sortable ?? true,
85
+ render: (item: any) => {
86
+ const value = item[key];
87
+ if (!value) return "";
88
+
89
+ const date = value instanceof Date ? value : new Date(value);
90
+ if (Number.isNaN(date.getTime())) return "";
91
+
92
+ const formatOptions: Intl.DateTimeFormatOptions = {
93
+ short: { dateStyle: "short" },
94
+ medium: { dateStyle: "medium" },
95
+ long: { dateStyle: "long" },
96
+ full: { dateStyle: "full" },
97
+ }[options?.format ?? "medium"] as Intl.DateTimeFormatOptions;
98
+
99
+ return date.toLocaleDateString(options?.locale, formatOptions);
100
+ },
101
+ }),
102
+
103
+ /**
104
+ * DateTime column with formatting
105
+ *
106
+ * @example
107
+ * columns.dateTime('createdAt', 'Created At')
108
+ */
109
+ dateTime: <T = any>(key: string, label: string, options?: { sortable?: boolean; locale?: string }): DataTableColumn<T> => ({
110
+ key,
111
+ label,
112
+ type: "date",
113
+ sortable: options?.sortable ?? true,
114
+ render: (item: any) => {
115
+ const value = item[key];
116
+ if (!value) return "";
117
+
118
+ const date = value instanceof Date ? value : new Date(value);
119
+ if (Number.isNaN(date.getTime())) return "";
120
+
121
+ return date.toLocaleString(options?.locale, {
122
+ dateStyle: "medium",
123
+ timeStyle: "short",
124
+ });
125
+ },
126
+ }),
127
+
128
+ /**
129
+ * Tag column with optional color function
130
+ *
131
+ * @example
132
+ * columns.tag('status', 'Status')
133
+ * columns.tag('status', 'Status', (item) => item.active ? 'green' : 'red')
134
+ */
135
+ tag: <T = any>(key: string, label: string, colorFn?: TagColorFn<T>, options?: { sortable?: boolean }): DataTableColumn<T> => ({
136
+ key,
137
+ label,
138
+ type: "tag",
139
+ sortable: options?.sortable ?? true,
140
+ render: (item: T) => {
141
+ const value = (item as any)[key];
142
+ return {
143
+ content: value,
144
+ color: colorFn?.(item) ?? "gray",
145
+ type: "tag",
146
+ };
147
+ },
148
+ }),
149
+
150
+ /**
151
+ * Badge column (numeric badge style)
152
+ *
153
+ * @example
154
+ * columns.badge('count', 'Count')
155
+ * columns.badge('brandCount', 'Brands')
156
+ */
157
+ badge: <T = any>(key: string, label: string, options?: { sortable?: boolean }): DataTableColumn<T> => ({
158
+ key,
159
+ label,
160
+ type: "badge",
161
+ sortable: options?.sortable ?? true,
162
+ render: (item: any) => ({
163
+ content: item[key],
164
+ type: "badge",
165
+ }),
166
+ }),
167
+
168
+ /**
169
+ * Boolean column rendered as tag
170
+ *
171
+ * @example
172
+ * columns.boolean('active', 'Active')
173
+ * columns.boolean('verified', 'Verified', { trueLabel: 'Yes', falseLabel: 'No' })
174
+ */
175
+ boolean: <T = any>(
176
+ key: string,
177
+ label: string,
178
+ options?: { trueLabel?: string; falseLabel?: string; trueColor?: string; falseColor?: string; sortable?: boolean }
179
+ ): DataTableColumn<T> => ({
180
+ key,
181
+ label,
182
+ type: "tag",
183
+ sortable: options?.sortable ?? true,
184
+ render: (item: any) => {
185
+ const value = Boolean(item[key]);
186
+ return {
187
+ content: value ? (options?.trueLabel ?? "Yes") : (options?.falseLabel ?? "No"),
188
+ color: value ? (options?.trueColor ?? "green") : (options?.falseColor ?? "red"),
189
+ type: "tag",
190
+ };
191
+ },
192
+ }),
193
+
194
+ /**
195
+ * Email column (renders as link)
196
+ *
197
+ * @example
198
+ * columns.email('email', 'Email')
199
+ */
200
+ email: <T = any>(key: string, label: string, options?: { sortable?: boolean }): DataTableColumn<T> => ({
201
+ key,
202
+ label,
203
+ type: "text",
204
+ sortable: options?.sortable ?? true,
205
+ render: (item: any) => ({
206
+ content: item[key],
207
+ type: "link",
208
+ href: `mailto:${item[key]}`,
209
+ }),
210
+ }),
211
+
212
+ /**
213
+ * Actions column (row actions)
214
+ *
215
+ * @example
216
+ * columns.actions(['view', 'edit', 'delete'])
217
+ */
218
+ actions: <T = any>(actions: string[]): DataTableColumn<T> => ({
219
+ key: "_actions",
220
+ label: "",
221
+ sortable: false,
222
+ render: () => actions,
223
+ }),
224
+
225
+ /**
226
+ * Custom render column
227
+ *
228
+ * @example
229
+ * columns.custom('fullName', 'Name', (item) => `${item.firstName} ${item.lastName}`)
230
+ */
231
+ custom: <T = any>(
232
+ key: string,
233
+ label: string,
234
+ render: (item: T, index: number) => any,
235
+ options?: { sortable?: boolean; width?: string }
236
+ ): DataTableColumn<T> => ({
237
+ key,
238
+ label,
239
+ sortable: options?.sortable ?? false,
240
+ width: options?.width,
241
+ render,
242
+ }),
243
+
244
+ /**
245
+ * Truncated text column
246
+ *
247
+ * @example
248
+ * columns.truncate('description', 'Description', 50)
249
+ */
250
+ truncate: <T = any>(key: string, label: string, maxLength: number, options?: { sortable?: boolean }): DataTableColumn<T> => ({
251
+ key,
252
+ label,
253
+ type: "text",
254
+ sortable: options?.sortable ?? true,
255
+ render: (item: any) => {
256
+ const value = String(item[key] ?? "");
257
+ if (value.length <= maxLength) return value;
258
+ return `${value.substring(0, maxLength)}...`;
259
+ },
260
+ }),
261
+
262
+ /**
263
+ * Image column
264
+ *
265
+ * @example
266
+ * columns.image('avatar', 'Avatar', { size: 32 })
267
+ */
268
+ image: <T = any>(key: string, label: string, options?: { size?: number; fallback?: string }): DataTableColumn<T> => ({
269
+ key,
270
+ label,
271
+ sortable: false,
272
+ render: (item: any) => ({
273
+ content: item[key] || options?.fallback,
274
+ type: "image",
275
+ size: options?.size ?? 32,
276
+ }),
277
+ }),
278
+
279
+ /**
280
+ * Progress column
281
+ *
282
+ * @example
283
+ * columns.progress('completion', 'Progress')
284
+ */
285
+ progress: <T = any>(key: string, label: string, options?: { max?: number; sortable?: boolean }): DataTableColumn<T> => ({
286
+ key,
287
+ label,
288
+ type: "number",
289
+ sortable: options?.sortable ?? true,
290
+ render: (item: any) => ({
291
+ content: item[key],
292
+ type: "progress",
293
+ max: options?.max ?? 100,
294
+ }),
295
+ }),
296
+
297
+ /**
298
+ * Clickable link column that emits an action
299
+ *
300
+ * @example
301
+ * // Simple link with action
302
+ * columns.link('name', 'Campaign Name', (item) => ({
303
+ * action: 'view',
304
+ * data: { id: item.id }
305
+ * }))
306
+ *
307
+ * // Link with custom emit handler in parent
308
+ * columns.link('title', 'Title', (item) => ({
309
+ * action: 'openDetails',
310
+ * data: item
311
+ * }))
312
+ */
313
+ link: <T = any>(
314
+ key: string,
315
+ label: string,
316
+ emitConfig: (item: T) => { action: string; data: any },
317
+ options?: { sortable?: boolean; width?: string }
318
+ ): DataTableColumn<T> => ({
319
+ key,
320
+ label,
321
+ sortable: options?.sortable ?? true,
322
+ width: options?.width,
323
+ render: (item: T) => ({
324
+ content: (item as any)[key],
325
+ type: "link",
326
+ emits: emitConfig(item),
327
+ }),
328
+ }),
329
+
330
+ /**
331
+ * Link to external URL
332
+ *
333
+ * @example
334
+ * columns.externalLink('website', 'Website')
335
+ * columns.externalLink('url', 'URL', { newTab: true })
336
+ */
337
+ externalLink: <T = any>(
338
+ key: string,
339
+ label: string,
340
+ options?: { sortable?: boolean; newTab?: boolean; displayKey?: string }
341
+ ): DataTableColumn<T> => ({
342
+ key,
343
+ label,
344
+ sortable: options?.sortable ?? true,
345
+ render: (item: any) => {
346
+ const url = item[key];
347
+ const display = options?.displayKey ? item[options.displayKey] : url;
348
+ return {
349
+ content: display,
350
+ type: "link",
351
+ href: url,
352
+ target: options?.newTab ? "_blank" : undefined,
353
+ };
354
+ },
355
+ }),
356
+
357
+ /**
358
+ * Relative time column (e.g., "2 hours ago")
359
+ *
360
+ * @example
361
+ * columns.relativeTime('updatedAt', 'Last Updated')
362
+ */
363
+ relativeTime: <T = any>(key: string, label: string, options?: { sortable?: boolean; locale?: string }): DataTableColumn<T> => ({
364
+ key,
365
+ label,
366
+ type: "date",
367
+ sortable: options?.sortable ?? true,
368
+ render: (item: any) => {
369
+ const value = item[key];
370
+ if (!value) return "";
371
+
372
+ const date = value instanceof Date ? value : new Date(value);
373
+ if (Number.isNaN(date.getTime())) return "";
374
+
375
+ const now = new Date();
376
+ const diffMs = now.getTime() - date.getTime();
377
+ const diffSecs = Math.floor(diffMs / 1000);
378
+ const diffMins = Math.floor(diffSecs / 60);
379
+ const diffHours = Math.floor(diffMins / 60);
380
+ const diffDays = Math.floor(diffHours / 24);
381
+
382
+ if (diffSecs < 60) return "Just now";
383
+ if (diffMins < 60) return `${diffMins}m ago`;
384
+ if (diffHours < 24) return `${diffHours}h ago`;
385
+ if (diffDays < 7) return `${diffDays}d ago`;
386
+
387
+ return date.toLocaleDateString(options?.locale);
388
+ },
389
+ }),
390
+
391
+ /**
392
+ * User/Avatar column with name and optional subtitle
393
+ *
394
+ * @example
395
+ * columns.user('assignee', 'Assigned To', { avatarKey: 'avatar', subtitleKey: 'email' })
396
+ */
397
+ user: <T = any>(
398
+ key: string,
399
+ label: string,
400
+ options?: { avatarKey?: string; subtitleKey?: string; sortable?: boolean }
401
+ ): DataTableColumn<T> => ({
402
+ key,
403
+ label,
404
+ sortable: options?.sortable ?? true,
405
+ render: (item: any) => ({
406
+ content: item[key],
407
+ type: "guest",
408
+ avatar: options?.avatarKey ? item[options.avatarKey] : undefined,
409
+ subtitle: options?.subtitleKey ? item[options.subtitleKey] : undefined,
410
+ }),
411
+ }),
412
+
413
+ /**
414
+ * Currency column with formatting
415
+ *
416
+ * @example
417
+ * columns.currency('amount', 'Total', { currency: 'EUR' })
418
+ * columns.currency('price', 'Price', { currency: 'USD', locale: 'en-US' })
419
+ */
420
+ currency: <T = any>(
421
+ key: string,
422
+ label: string,
423
+ options?: { currency?: string; locale?: string; sortable?: boolean }
424
+ ): DataTableColumn<T> => ({
425
+ key,
426
+ label,
427
+ type: "number",
428
+ sortable: options?.sortable ?? true,
429
+ render: (item: any) => {
430
+ const value = item[key];
431
+ if (value === null || value === undefined) return "";
432
+
433
+ return new Intl.NumberFormat(options?.locale ?? "en-US", {
434
+ style: "currency",
435
+ currency: options?.currency ?? "EUR",
436
+ }).format(Number(value));
437
+ },
438
+ }),
439
+
440
+ /**
441
+ * Percentage column
442
+ *
443
+ * @example
444
+ * columns.percentage('openRate', 'Open Rate')
445
+ * columns.percentage('completion', 'Progress', { decimals: 0 })
446
+ */
447
+ percentage: <T = any>(
448
+ key: string,
449
+ label: string,
450
+ options?: { decimals?: number; sortable?: boolean; multiply?: boolean }
451
+ ): DataTableColumn<T> => ({
452
+ key,
453
+ label,
454
+ type: "number",
455
+ sortable: options?.sortable ?? true,
456
+ render: (item: any) => {
457
+ const value = item[key];
458
+ if (value === null || value === undefined) return "";
459
+
460
+ // If multiply is true, assume value is 0-1 and needs *100
461
+ const numValue = options?.multiply ? Number(value) * 100 : Number(value);
462
+ return `${numValue.toFixed(options?.decimals ?? 1)}%`;
463
+ },
464
+ }),
465
+ };
466
+
467
+ /**
468
+ * Status color mappings for common status values
469
+ */
470
+ export const statusColors: Record<string, string> = {
471
+ active: "green",
472
+ inactive: "gray",
473
+ pending: "yellow",
474
+ approved: "green",
475
+ rejected: "red",
476
+ draft: "gray",
477
+ published: "green",
478
+ archived: "gray",
479
+ error: "red",
480
+ success: "green",
481
+ warning: "yellow",
482
+ info: "blue",
483
+ };
484
+
485
+ /**
486
+ * Get color for a status value
487
+ *
488
+ * @example
489
+ * columns.tag('status', 'Status', (item) => getStatusColor(item.status))
490
+ */
491
+ export function getStatusColor(status: string): string {
492
+ return statusColors[status?.toLowerCase()] ?? "gray";
493
+ }
494
+
495
+ /**
496
+ * Create a status color function for a specific key
497
+ *
498
+ * @example
499
+ * columns.tag('status', 'Status', createStatusColorFn('status'))
500
+ */
501
+ export function createStatusColorFn<T>(key: keyof T): TagColorFn<T> {
502
+ return (item: T) => getStatusColor(String(item[key]));
503
+ }
@@ -0,0 +1,106 @@
1
+ # Data Module
2
+
3
+ Components for displaying and managing data.
4
+
5
+ ## Components
6
+
7
+ ### Table
8
+
9
+ Comprehensive data table with pagination, sorting, and filtering.
10
+
11
+ ```vue
12
+ <script setup>
13
+ import { ref } from 'vue';
14
+ import { Table } from '@htlkg/components/data';
15
+
16
+ const currentPage = ref(1);
17
+ const columns = [
18
+ { name: 'Name', value: 'name', sortable: true },
19
+ { name: 'Email', value: 'email' },
20
+ { name: 'Status', value: 'status' },
21
+ ];
22
+ </script>
23
+
24
+ <template>
25
+ <Table
26
+ v-model:currentPage="currentPage"
27
+ :items="users"
28
+ :columns="columns"
29
+ :loading="loading"
30
+ selectable
31
+ @row-click="handleRowClick"
32
+ @selection-change="handleSelection"
33
+ />
34
+ </template>
35
+ ```
36
+
37
+ ### DataList
38
+
39
+ Simple list component for displaying collections.
40
+
41
+ ```vue
42
+ <script setup>
43
+ import { DataList } from '@htlkg/components/data';
44
+ </script>
45
+
46
+ <template>
47
+ <DataList :items="items" :loading="loading">
48
+ <template #item="{ item }">
49
+ <div>{{ item.name }}</div>
50
+ </template>
51
+ <template #empty>
52
+ <p>No items found</p>
53
+ </template>
54
+ </DataList>
55
+ </template>
56
+ ```
57
+
58
+ ### SearchableSelect
59
+
60
+ Searchable dropdown with single/multiple selection.
61
+
62
+ ```vue
63
+ <script setup>
64
+ import { ref } from 'vue';
65
+ import { SearchableSelect } from '@htlkg/components/data';
66
+
67
+ const selected = ref(null);
68
+ const options = [
69
+ { value: '1', label: 'Option 1' },
70
+ { value: '2', label: 'Option 2' },
71
+ ];
72
+ </script>
73
+
74
+ <template>
75
+ <SearchableSelect
76
+ v-model="selected"
77
+ :options="options"
78
+ placeholder="Select..."
79
+ searchable
80
+ clearable
81
+ />
82
+ </template>
83
+ ```
84
+
85
+ ### Chart
86
+
87
+ Data visualization with multiple chart types.
88
+
89
+ ```vue
90
+ <script setup>
91
+ import { Chart } from '@htlkg/components/data';
92
+
93
+ const data = {
94
+ labels: ['Jan', 'Feb', 'Mar'],
95
+ datasets: [{ data: [10, 20, 30] }],
96
+ };
97
+ </script>
98
+
99
+ <template>
100
+ <Chart
101
+ type="line"
102
+ :data="data"
103
+ :options="{ responsive: true }"
104
+ />
105
+ </template>
106
+ ```