@veristone/nuxt-v-app 0.2.2 → 0.2.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.
- package/app/components/V/A/Crud/Delete.vue +1 -0
- package/app/components/V/A/CrudTable/index.vue +486 -0
- package/app/components/V/A/Table/ActionColumn.vue +133 -0
- package/app/components/V/A/Table/Actions.vue +79 -0
- package/app/components/V/A/Table/CellRenderer.vue +198 -0
- package/app/components/V/A/Table/ColumnToggle.vue +131 -0
- package/app/components/V/A/Table/EditableCell.vue +176 -0
- package/app/components/V/A/Table/Export.vue +154 -0
- package/app/components/V/A/Table/FilterBar.vue +140 -0
- package/app/components/V/A/Table/FilterChips.vue +107 -0
- package/app/components/V/A/Table/README.md +380 -0
- package/app/components/V/A/Table/Toolbar.vue +163 -0
- package/app/components/V/A/Table/index.vue +483 -0
- package/app/composables/useDataTable.js +169 -0
- package/app/composables/useXATableColumns.ts +279 -386
- package/app/pages/playground/tables.vue +182 -553
- package/app/types/table.ts +52 -0
- package/package.json +4 -2
- package/app/components/V/A/Table.vue +0 -674
|
@@ -1,456 +1,349 @@
|
|
|
1
|
-
import { h, resolveComponent } from 'vue'
|
|
2
|
-
import type { TableColumn } from '@nuxt/ui'
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
2
|
+
* useXATableColumns - Column presets for common data types
|
|
3
|
+
*
|
|
4
|
+
* Provides pre-configured column definitions that automatically render
|
|
5
|
+
* with appropriate formatting and components via CellRenderer.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const { presets } = useXATableColumns();
|
|
10
|
+
*
|
|
11
|
+
* const tableColumns = [
|
|
12
|
+
* presets.avatar('name', 'Name'),
|
|
13
|
+
* presets.text('email', 'Email'),
|
|
14
|
+
* presets.badge('status', 'Status'),
|
|
15
|
+
* presets.date('createdAt', 'Created'),
|
|
16
|
+
* presets.currency('price', 'Price'),
|
|
17
|
+
* presets.actions(),
|
|
18
|
+
* ];
|
|
19
|
+
* ```
|
|
6
20
|
*/
|
|
7
|
-
export function useXATableColumns<T extends Record<string, any>>() {
|
|
8
|
-
const UBadge = resolveComponent('UBadge')
|
|
9
|
-
const UButton = resolveComponent('UButton')
|
|
10
|
-
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
|
11
|
-
const UAvatar = resolveComponent('UAvatar')
|
|
12
21
|
|
|
22
|
+
import type { TableColumn } from '@nuxt/ui';
|
|
23
|
+
|
|
24
|
+
export interface ColumnPresetOptions {
|
|
25
|
+
/** Column header text */
|
|
26
|
+
header?: string;
|
|
27
|
+
/** Enable sorting (default: true) */
|
|
28
|
+
sortable?: boolean;
|
|
29
|
+
/** Enable hiding (default: true) */
|
|
30
|
+
hideable?: boolean;
|
|
31
|
+
/** Additional TanStack column options */
|
|
32
|
+
columnOptions?: Partial<TableColumn<any>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DateColumnOptions extends ColumnPresetOptions {
|
|
36
|
+
/** Date format: 'date', 'time', 'datetime', 'relative' */
|
|
37
|
+
format?: 'date' | 'time' | 'datetime' | 'relative';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CurrencyColumnOptions extends ColumnPresetOptions {
|
|
41
|
+
/** Currency code (default: 'USD') */
|
|
42
|
+
currency?: string;
|
|
43
|
+
/** Locale (default: 'en-US') */
|
|
44
|
+
locale?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface NumberColumnOptions extends ColumnPresetOptions {
|
|
48
|
+
/** Decimal places */
|
|
49
|
+
decimals?: number;
|
|
50
|
+
/** Locale (default: 'en-US') */
|
|
51
|
+
locale?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BadgeColumnOptions extends ColumnPresetOptions {
|
|
55
|
+
/** Custom color mapping */
|
|
56
|
+
colorMap?: Record<string, 'primary' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | 'secondary'>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AvatarColumnOptions extends ColumnPresetOptions {
|
|
60
|
+
/** Avatar size: 'xs', 'sm', 'md', 'lg', 'xl' */
|
|
61
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
62
|
+
/** Show name text alongside avatar */
|
|
63
|
+
showName?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ActionsColumnOptions {
|
|
67
|
+
/** Show view button */
|
|
68
|
+
showView?: boolean;
|
|
69
|
+
/** Show edit button */
|
|
70
|
+
showEdit?: boolean;
|
|
71
|
+
/** Show delete button */
|
|
72
|
+
showDelete?: boolean;
|
|
73
|
+
/** Maximum visible actions before overflow */
|
|
74
|
+
maxVisible?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface EditableColumnOptions extends ColumnPresetOptions {
|
|
78
|
+
/** Input type for editing */
|
|
79
|
+
inputType?: 'text' | 'number' | 'email' | 'textarea' | 'select' | 'boolean';
|
|
80
|
+
/** Options for select input */
|
|
81
|
+
selectOptions?: Array<string | { label: string; value: unknown }>;
|
|
82
|
+
/** Custom formatter for display value */
|
|
83
|
+
formatter?: (value: unknown) => string;
|
|
84
|
+
/** Validation rules */
|
|
85
|
+
validation?: {
|
|
86
|
+
required?: boolean;
|
|
87
|
+
min?: number;
|
|
88
|
+
max?: number;
|
|
89
|
+
pattern?: RegExp;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function useXATableColumns() {
|
|
13
94
|
/**
|
|
14
|
-
* Create a text column
|
|
95
|
+
* Create a basic text column
|
|
15
96
|
*/
|
|
16
|
-
function
|
|
17
|
-
accessorKey: keyof T & string,
|
|
18
|
-
header: string,
|
|
19
|
-
options?: {
|
|
20
|
-
truncate?: boolean
|
|
21
|
-
maxWidth?: string
|
|
22
|
-
class?: string
|
|
23
|
-
}
|
|
24
|
-
): TableColumn<T> {
|
|
97
|
+
function text(key: string, header?: string, options: ColumnPresetOptions = {}): TableColumn<any> {
|
|
25
98
|
return {
|
|
26
|
-
accessorKey,
|
|
27
|
-
header,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
classes.push('truncate')
|
|
33
|
-
}
|
|
34
|
-
return h('span', {
|
|
35
|
-
class: classes.filter(Boolean).join(' '),
|
|
36
|
-
style: options?.maxWidth ? { maxWidth: options.maxWidth } : undefined,
|
|
37
|
-
title: options?.truncate ? String(value) : undefined
|
|
38
|
-
}, String(value ?? ''))
|
|
39
|
-
}
|
|
40
|
-
}
|
|
99
|
+
accessorKey: key,
|
|
100
|
+
header: header || formatHeader(key),
|
|
101
|
+
enableSorting: options.sortable !== false,
|
|
102
|
+
enableHiding: options.hideable !== false,
|
|
103
|
+
...options.columnOptions,
|
|
104
|
+
};
|
|
41
105
|
}
|
|
42
106
|
|
|
43
107
|
/**
|
|
44
|
-
* Create
|
|
108
|
+
* Create an avatar column with optional name
|
|
45
109
|
*/
|
|
46
|
-
function
|
|
47
|
-
accessorKey: keyof T & string,
|
|
48
|
-
header: string,
|
|
49
|
-
options?: {
|
|
50
|
-
format?: Intl.DateTimeFormatOptions
|
|
51
|
-
locale?: string
|
|
52
|
-
relative?: boolean
|
|
53
|
-
}
|
|
54
|
-
): TableColumn<T> {
|
|
55
|
-
const defaultFormat: Intl.DateTimeFormatOptions = {
|
|
56
|
-
day: 'numeric',
|
|
57
|
-
month: 'short',
|
|
58
|
-
year: 'numeric'
|
|
59
|
-
}
|
|
60
|
-
|
|
110
|
+
function avatar(key: string, header?: string, options: AvatarColumnOptions = {}): TableColumn<any> {
|
|
61
111
|
return {
|
|
62
|
-
accessorKey,
|
|
63
|
-
header,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return date.toLocaleDateString(
|
|
76
|
-
options?.locale ?? 'en-US',
|
|
77
|
-
options?.format ?? defaultFormat
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
112
|
+
accessorKey: key,
|
|
113
|
+
header: header || formatHeader(key),
|
|
114
|
+
enableSorting: options.sortable !== false,
|
|
115
|
+
enableHiding: options.hideable !== false,
|
|
116
|
+
meta: {
|
|
117
|
+
preset: 'avatar',
|
|
118
|
+
size: options.size || 'xs',
|
|
119
|
+
showName: options.showName !== false,
|
|
120
|
+
},
|
|
121
|
+
...options.columnOptions,
|
|
122
|
+
};
|
|
81
123
|
}
|
|
82
124
|
|
|
83
125
|
/**
|
|
84
|
-
* Create a
|
|
126
|
+
* Create a badge/status column
|
|
85
127
|
*/
|
|
86
|
-
function
|
|
87
|
-
accessorKey: keyof T & string,
|
|
88
|
-
header: string,
|
|
89
|
-
options?: {
|
|
90
|
-
format?: Intl.DateTimeFormatOptions
|
|
91
|
-
locale?: string
|
|
92
|
-
}
|
|
93
|
-
): TableColumn<T> {
|
|
94
|
-
const defaultFormat: Intl.DateTimeFormatOptions = {
|
|
95
|
-
day: 'numeric',
|
|
96
|
-
month: 'short',
|
|
97
|
-
hour: '2-digit',
|
|
98
|
-
minute: '2-digit',
|
|
99
|
-
hour12: false
|
|
100
|
-
}
|
|
101
|
-
|
|
128
|
+
function badge(key: string, header?: string, options: BadgeColumnOptions = {}): TableColumn<any> {
|
|
102
129
|
return {
|
|
103
|
-
accessorKey,
|
|
104
|
-
header,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
options?.locale ?? 'en-US',
|
|
114
|
-
options?.format ?? defaultFormat
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
130
|
+
accessorKey: key,
|
|
131
|
+
header: header || formatHeader(key),
|
|
132
|
+
enableSorting: options.sortable !== false,
|
|
133
|
+
enableHiding: options.hideable !== false,
|
|
134
|
+
meta: {
|
|
135
|
+
preset: 'badge',
|
|
136
|
+
colorMap: options.colorMap,
|
|
137
|
+
},
|
|
138
|
+
...options.columnOptions,
|
|
139
|
+
};
|
|
118
140
|
}
|
|
119
141
|
|
|
120
142
|
/**
|
|
121
|
-
* Create a
|
|
143
|
+
* Create a date/time column
|
|
122
144
|
*/
|
|
123
|
-
function
|
|
124
|
-
accessorKey: keyof T & string,
|
|
125
|
-
header: string,
|
|
126
|
-
options?: {
|
|
127
|
-
currency?: string
|
|
128
|
-
locale?: string
|
|
129
|
-
minimumFractionDigits?: number
|
|
130
|
-
maximumFractionDigits?: number
|
|
131
|
-
}
|
|
132
|
-
): TableColumn<T> {
|
|
145
|
+
function date(key: string, header?: string, options: DateColumnOptions = {}): TableColumn<any> {
|
|
133
146
|
return {
|
|
134
|
-
accessorKey,
|
|
135
|
-
header:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
style: 'currency',
|
|
145
|
-
currency: options?.currency ?? 'USD',
|
|
146
|
-
minimumFractionDigits: options?.minimumFractionDigits ?? 2,
|
|
147
|
-
maximumFractionDigits: options?.maximumFractionDigits ?? 2
|
|
148
|
-
}).format(amount)
|
|
149
|
-
|
|
150
|
-
return h('div', { class: 'text-right font-medium' }, formatted)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
147
|
+
accessorKey: key,
|
|
148
|
+
header: header || formatHeader(key),
|
|
149
|
+
enableSorting: options.sortable !== false,
|
|
150
|
+
enableHiding: options.hideable !== false,
|
|
151
|
+
meta: {
|
|
152
|
+
preset: 'date',
|
|
153
|
+
format: options.format || 'date',
|
|
154
|
+
},
|
|
155
|
+
...options.columnOptions,
|
|
156
|
+
};
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
/**
|
|
156
|
-
* Create a
|
|
160
|
+
* Create a currency column
|
|
157
161
|
*/
|
|
158
|
-
function
|
|
159
|
-
accessorKey: keyof T & string,
|
|
160
|
-
header: string,
|
|
161
|
-
options?: {
|
|
162
|
-
locale?: string
|
|
163
|
-
minimumFractionDigits?: number
|
|
164
|
-
maximumFractionDigits?: number
|
|
165
|
-
align?: 'left' | 'center' | 'right'
|
|
166
|
-
}
|
|
167
|
-
): TableColumn<T> {
|
|
168
|
-
const align = options?.align ?? 'right'
|
|
169
|
-
const alignClass = {
|
|
170
|
-
left: 'text-left',
|
|
171
|
-
center: 'text-center',
|
|
172
|
-
right: 'text-right'
|
|
173
|
-
}[align]
|
|
174
|
-
|
|
162
|
+
function currency(key: string, header?: string, options: CurrencyColumnOptions = {}): TableColumn<any> {
|
|
175
163
|
return {
|
|
176
|
-
accessorKey,
|
|
177
|
-
header:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
maximumFractionDigits: options?.maximumFractionDigits
|
|
188
|
-
}).format(num)
|
|
189
|
-
|
|
190
|
-
return h('div', { class: alignClass }, formatted)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
164
|
+
accessorKey: key,
|
|
165
|
+
header: header || formatHeader(key),
|
|
166
|
+
enableSorting: options.sortable !== false,
|
|
167
|
+
enableHiding: options.hideable !== false,
|
|
168
|
+
meta: {
|
|
169
|
+
preset: 'currency',
|
|
170
|
+
currency: options.currency || 'USD',
|
|
171
|
+
locale: options.locale || 'en-US',
|
|
172
|
+
},
|
|
173
|
+
...options.columnOptions,
|
|
174
|
+
};
|
|
193
175
|
}
|
|
194
176
|
|
|
195
177
|
/**
|
|
196
|
-
* Create a
|
|
178
|
+
* Create a number column with formatting
|
|
197
179
|
*/
|
|
198
|
-
function
|
|
199
|
-
accessorKey: K,
|
|
200
|
-
header: string,
|
|
201
|
-
colorMap: Record<string, 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'>,
|
|
202
|
-
options?: {
|
|
203
|
-
variant?: 'solid' | 'outline' | 'soft' | 'subtle'
|
|
204
|
-
capitalize?: boolean
|
|
205
|
-
labelMap?: Record<string, string>
|
|
206
|
-
}
|
|
207
|
-
): TableColumn<T> {
|
|
180
|
+
function number(key: string, header?: string, options: NumberColumnOptions = {}): TableColumn<any> {
|
|
208
181
|
return {
|
|
209
|
-
accessorKey,
|
|
210
|
-
header,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}, () => label)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
182
|
+
accessorKey: key,
|
|
183
|
+
header: header || formatHeader(key),
|
|
184
|
+
enableSorting: options.sortable !== false,
|
|
185
|
+
enableHiding: options.hideable !== false,
|
|
186
|
+
meta: {
|
|
187
|
+
preset: 'number',
|
|
188
|
+
decimals: options.decimals,
|
|
189
|
+
locale: options.locale || 'en-US',
|
|
190
|
+
},
|
|
191
|
+
...options.columnOptions,
|
|
192
|
+
};
|
|
223
193
|
}
|
|
224
194
|
|
|
225
195
|
/**
|
|
226
|
-
* Create a boolean column
|
|
196
|
+
* Create a boolean/checkbox column
|
|
227
197
|
*/
|
|
228
|
-
function
|
|
229
|
-
accessorKey: keyof T & string,
|
|
230
|
-
header: string,
|
|
231
|
-
options?: {
|
|
232
|
-
trueLabel?: string
|
|
233
|
-
falseLabel?: string
|
|
234
|
-
trueColor?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
|
|
235
|
-
falseColor?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
|
|
236
|
-
}
|
|
237
|
-
): TableColumn<T> {
|
|
198
|
+
function boolean(key: string, header?: string, options: ColumnPresetOptions = {}): TableColumn<any> {
|
|
238
199
|
return {
|
|
239
|
-
accessorKey,
|
|
240
|
-
header,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
200
|
+
accessorKey: key,
|
|
201
|
+
header: header || formatHeader(key),
|
|
202
|
+
enableSorting: options.sortable !== false,
|
|
203
|
+
enableHiding: options.hideable !== false,
|
|
204
|
+
meta: {
|
|
205
|
+
preset: 'boolean',
|
|
206
|
+
},
|
|
207
|
+
...options.columnOptions,
|
|
208
|
+
};
|
|
249
209
|
}
|
|
250
210
|
|
|
251
211
|
/**
|
|
252
|
-
* Create an
|
|
212
|
+
* Create an email column with link
|
|
253
213
|
*/
|
|
254
|
-
function
|
|
255
|
-
accessorKey: keyof T & string,
|
|
256
|
-
header: string,
|
|
257
|
-
options?: {
|
|
258
|
-
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
259
|
-
nameKey?: keyof T & string
|
|
260
|
-
fallbackIcon?: string
|
|
261
|
-
}
|
|
262
|
-
): TableColumn<T> {
|
|
214
|
+
function email(key: string, header?: string, options: ColumnPresetOptions = {}): TableColumn<any> {
|
|
263
215
|
return {
|
|
264
|
-
accessorKey,
|
|
265
|
-
header,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
size: options?.size ?? 'sm',
|
|
274
|
-
icon: options?.fallbackIcon ?? 'i-lucide-user'
|
|
275
|
-
})
|
|
276
|
-
}
|
|
277
|
-
}
|
|
216
|
+
accessorKey: key,
|
|
217
|
+
header: header || 'Email',
|
|
218
|
+
enableSorting: options.sortable !== false,
|
|
219
|
+
enableHiding: options.hideable !== false,
|
|
220
|
+
meta: {
|
|
221
|
+
preset: 'email',
|
|
222
|
+
},
|
|
223
|
+
...options.columnOptions,
|
|
224
|
+
};
|
|
278
225
|
}
|
|
279
226
|
|
|
280
227
|
/**
|
|
281
|
-
* Create a
|
|
228
|
+
* Create a link/URL column
|
|
282
229
|
*/
|
|
283
|
-
function
|
|
284
|
-
config: {
|
|
285
|
-
avatarKey?: keyof T & string
|
|
286
|
-
nameKey: keyof T & string
|
|
287
|
-
emailKey?: keyof T & string
|
|
288
|
-
},
|
|
289
|
-
header: string
|
|
290
|
-
): TableColumn<T> {
|
|
230
|
+
function link(key: string, header?: string, options: ColumnPresetOptions = {}): TableColumn<any> {
|
|
291
231
|
return {
|
|
292
|
-
|
|
293
|
-
header,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
h(UAvatar, {
|
|
302
|
-
src: avatar,
|
|
303
|
-
alt: name,
|
|
304
|
-
size: 'sm',
|
|
305
|
-
icon: 'i-lucide-user'
|
|
306
|
-
}),
|
|
307
|
-
h('div', { class: 'flex flex-col' }, [
|
|
308
|
-
h('span', { class: 'font-medium text-gray-900 dark:text-white' }, name),
|
|
309
|
-
email ? h('span', { class: 'text-sm text-gray-500 dark:text-gray-400' }, email) : null
|
|
310
|
-
].filter(Boolean))
|
|
311
|
-
])
|
|
312
|
-
}
|
|
313
|
-
}
|
|
232
|
+
accessorKey: key,
|
|
233
|
+
header: header || formatHeader(key),
|
|
234
|
+
enableSorting: options.sortable !== false,
|
|
235
|
+
enableHiding: options.hideable !== false,
|
|
236
|
+
meta: {
|
|
237
|
+
preset: 'link',
|
|
238
|
+
},
|
|
239
|
+
...options.columnOptions,
|
|
240
|
+
};
|
|
314
241
|
}
|
|
315
242
|
|
|
316
243
|
/**
|
|
317
|
-
* Create an actions column
|
|
244
|
+
* Create an actions column
|
|
318
245
|
*/
|
|
319
|
-
function
|
|
320
|
-
getActions: (row: T) => Array<{
|
|
321
|
-
label: string
|
|
322
|
-
icon?: string
|
|
323
|
-
color?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
|
|
324
|
-
disabled?: boolean
|
|
325
|
-
onClick: () => void
|
|
326
|
-
} | 'separator'>,
|
|
327
|
-
options?: {
|
|
328
|
-
header?: string
|
|
329
|
-
icon?: string
|
|
330
|
-
}
|
|
331
|
-
): TableColumn<T> {
|
|
246
|
+
function actions(options: ActionsColumnOptions = {}): TableColumn<any> {
|
|
332
247
|
return {
|
|
333
|
-
|
|
334
|
-
header:
|
|
335
|
-
cell: ({ row }) => {
|
|
336
|
-
const actions = getActions(row.original as T)
|
|
337
|
-
const items = actions.map(action => {
|
|
338
|
-
if (action === 'separator') {
|
|
339
|
-
return { type: 'separator' as const }
|
|
340
|
-
}
|
|
341
|
-
return {
|
|
342
|
-
label: action.label,
|
|
343
|
-
icon: action.icon,
|
|
344
|
-
color: action.color,
|
|
345
|
-
disabled: action.disabled,
|
|
346
|
-
onSelect: action.onClick
|
|
347
|
-
}
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
return h('div', { class: 'text-right' }, [
|
|
351
|
-
h(UDropdownMenu, {
|
|
352
|
-
items: [items]
|
|
353
|
-
}, {
|
|
354
|
-
default: () => h(UButton, {
|
|
355
|
-
icon: options?.icon ?? 'i-lucide-ellipsis-vertical',
|
|
356
|
-
color: 'neutral',
|
|
357
|
-
variant: 'ghost',
|
|
358
|
-
size: 'xs'
|
|
359
|
-
})
|
|
360
|
-
})
|
|
361
|
-
])
|
|
362
|
-
},
|
|
248
|
+
accessorKey: 'actions',
|
|
249
|
+
header: '',
|
|
363
250
|
enableSorting: false,
|
|
364
251
|
enableHiding: false,
|
|
365
|
-
|
|
366
|
-
|
|
252
|
+
meta: {
|
|
253
|
+
preset: 'actions',
|
|
254
|
+
showView: options.showView ?? true,
|
|
255
|
+
showEdit: options.showEdit ?? true,
|
|
256
|
+
showDelete: options.showDelete ?? true,
|
|
257
|
+
maxVisible: options.maxVisible ?? 2,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
367
260
|
}
|
|
368
261
|
|
|
369
262
|
/**
|
|
370
|
-
* Create
|
|
263
|
+
* Create an editable column (supports inline editing)
|
|
371
264
|
*/
|
|
372
|
-
function
|
|
373
|
-
accessorKey: keyof T & string,
|
|
374
|
-
header: string,
|
|
375
|
-
getHref: (row: T) => string,
|
|
376
|
-
options?: {
|
|
377
|
-
external?: boolean
|
|
378
|
-
class?: string
|
|
379
|
-
}
|
|
380
|
-
): TableColumn<T> {
|
|
265
|
+
function editable(key: string, header?: string, options: EditableColumnOptions = {}): TableColumn<any> {
|
|
381
266
|
return {
|
|
382
|
-
accessorKey,
|
|
383
|
-
header,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
267
|
+
accessorKey: key,
|
|
268
|
+
header: header || formatHeader(key),
|
|
269
|
+
enableSorting: options.sortable !== false,
|
|
270
|
+
enableHiding: options.hideable !== false,
|
|
271
|
+
meta: {
|
|
272
|
+
preset: 'editable',
|
|
273
|
+
inputType: options.inputType || 'text',
|
|
274
|
+
selectOptions: options.selectOptions,
|
|
275
|
+
formatter: options.formatter,
|
|
276
|
+
validation: options.validation,
|
|
277
|
+
},
|
|
278
|
+
...options.columnOptions,
|
|
279
|
+
};
|
|
396
280
|
}
|
|
397
281
|
|
|
398
282
|
/**
|
|
399
|
-
* Create a
|
|
283
|
+
* Create a custom column with a render function
|
|
400
284
|
*/
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
header: string
|
|
285
|
+
function custom<T = any>(
|
|
286
|
+
key: keyof T & string,
|
|
287
|
+
header: string,
|
|
288
|
+
options: ColumnPresetOptions = {}
|
|
404
289
|
): TableColumn<T> {
|
|
405
290
|
return {
|
|
406
|
-
accessorKey,
|
|
407
|
-
header
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
label: header,
|
|
413
|
-
icon: isSorted
|
|
414
|
-
? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow')
|
|
415
|
-
: 'i-lucide-arrow-up-down',
|
|
416
|
-
class: '-mx-2.5',
|
|
417
|
-
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
|
|
418
|
-
})
|
|
419
|
-
}
|
|
420
|
-
}
|
|
291
|
+
accessorKey: key as any,
|
|
292
|
+
header,
|
|
293
|
+
enableSorting: options.sortable !== false,
|
|
294
|
+
enableHiding: options.hideable !== false,
|
|
295
|
+
...options.columnOptions,
|
|
296
|
+
};
|
|
421
297
|
}
|
|
422
298
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
linkColumn,
|
|
435
|
-
sortableHeader
|
|
299
|
+
/**
|
|
300
|
+
* Helper to format accessor key into readable header
|
|
301
|
+
*/
|
|
302
|
+
function formatHeader(key: string): string {
|
|
303
|
+
return key
|
|
304
|
+
.replace(/([A-Z])/g, ' $1') // Add space before capitals
|
|
305
|
+
.replace(/[_-]/g, ' ') // Replace underscores and dashes
|
|
306
|
+
.replace(/^\s/, '') // Remove leading space
|
|
307
|
+
.split(' ')
|
|
308
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
309
|
+
.join(' ');
|
|
436
310
|
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Helper function for relative time
|
|
440
|
-
function formatRelativeTime(date: Date): string {
|
|
441
|
-
const now = new Date()
|
|
442
|
-
const diffMs = now.getTime() - date.getTime()
|
|
443
|
-
const diffSecs = Math.floor(diffMs / 1000)
|
|
444
|
-
const diffMins = Math.floor(diffSecs / 60)
|
|
445
|
-
const diffHours = Math.floor(diffMins / 60)
|
|
446
|
-
const diffDays = Math.floor(diffHours / 24)
|
|
447
311
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
312
|
+
// Presets object for easy access
|
|
313
|
+
const presets = {
|
|
314
|
+
text,
|
|
315
|
+
avatar,
|
|
316
|
+
badge,
|
|
317
|
+
date,
|
|
318
|
+
currency,
|
|
319
|
+
number,
|
|
320
|
+
boolean,
|
|
321
|
+
email,
|
|
322
|
+
link,
|
|
323
|
+
editable,
|
|
324
|
+
actions,
|
|
325
|
+
custom,
|
|
326
|
+
};
|
|
452
327
|
|
|
453
|
-
return
|
|
328
|
+
return {
|
|
329
|
+
// Individual preset functions
|
|
330
|
+
text,
|
|
331
|
+
avatar,
|
|
332
|
+
badge,
|
|
333
|
+
date,
|
|
334
|
+
currency,
|
|
335
|
+
number,
|
|
336
|
+
boolean,
|
|
337
|
+
email,
|
|
338
|
+
link,
|
|
339
|
+
editable,
|
|
340
|
+
actions,
|
|
341
|
+
custom,
|
|
342
|
+
|
|
343
|
+
// Presets object
|
|
344
|
+
presets,
|
|
345
|
+
|
|
346
|
+
// Utility
|
|
347
|
+
formatHeader,
|
|
348
|
+
};
|
|
454
349
|
}
|
|
455
|
-
|
|
456
|
-
export type { TableColumn }
|