@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.
@@ -1,456 +1,349 @@
1
- import { h, resolveComponent } from 'vue'
2
- import type { TableColumn } from '@nuxt/ui'
3
-
4
1
  /**
5
- * Composable for creating common table column patterns
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 with optional formatting
95
+ * Create a basic text column
15
96
  */
16
- function textColumn(
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
- cell: ({ row }) => {
29
- const value = row.getValue(accessorKey)
30
- const classes = [options?.class]
31
- if (options?.truncate) {
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 a date column with formatting
108
+ * Create an avatar column with optional name
45
109
  */
46
- function dateColumn(
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
- cell: ({ row }) => {
65
- const value = row.getValue(accessorKey)
66
- if (!value) return '-'
67
-
68
- const date = new Date(value as string | number | Date)
69
- if (isNaN(date.getTime())) return String(value)
70
-
71
- if (options?.relative) {
72
- return formatRelativeTime(date)
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 datetime column with time
126
+ * Create a badge/status column
85
127
  */
86
- function dateTimeColumn(
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
- cell: ({ row }) => {
106
- const value = row.getValue(accessorKey)
107
- if (!value) return '-'
108
-
109
- const date = new Date(value as string | number | Date)
110
- if (isNaN(date.getTime())) return String(value)
111
-
112
- return date.toLocaleString(
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 currency/money column
143
+ * Create a date/time column
122
144
  */
123
- function currencyColumn(
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: () => h('div', { class: 'text-right' }, header),
136
- cell: ({ row }) => {
137
- const value = row.getValue(accessorKey)
138
- if (value === null || value === undefined) return h('div', { class: 'text-right' }, '-')
139
-
140
- const amount = Number(value)
141
- if (isNaN(amount)) return h('div', { class: 'text-right' }, String(value))
142
-
143
- const formatted = new Intl.NumberFormat(options?.locale ?? 'en-US', {
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 number column with formatting
160
+ * Create a currency column
157
161
  */
158
- function numberColumn(
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: () => h('div', { class: alignClass }, header),
178
- cell: ({ row }) => {
179
- const value = row.getValue(accessorKey)
180
- if (value === null || value === undefined) return h('div', { class: alignClass }, '-')
181
-
182
- const num = Number(value)
183
- if (isNaN(num)) return h('div', { class: alignClass }, String(value))
184
-
185
- const formatted = new Intl.NumberFormat(options?.locale ?? 'en-US', {
186
- minimumFractionDigits: options?.minimumFractionDigits,
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 badge/status column
178
+ * Create a number column with formatting
197
179
  */
198
- function badgeColumn<K extends keyof T & string>(
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
- cell: ({ row }) => {
212
- const value = String(row.getValue(accessorKey) ?? '')
213
- const color = colorMap[value] ?? 'neutral'
214
- const label = options?.labelMap?.[value] ?? value
215
-
216
- return h(UBadge, {
217
- color,
218
- variant: options?.variant ?? 'subtle',
219
- class: options?.capitalize !== false ? 'capitalize' : ''
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 with badge display
196
+ * Create a boolean/checkbox column
227
197
  */
228
- function booleanColumn(
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
- cell: ({ row }) => {
242
- const value = Boolean(row.getValue(accessorKey))
243
- return h(UBadge, {
244
- color: value ? (options?.trueColor ?? 'success') : (options?.falseColor ?? 'neutral'),
245
- variant: 'subtle'
246
- }, () => value ? (options?.trueLabel ?? 'Yes') : (options?.falseLabel ?? 'No'))
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 avatar column
212
+ * Create an email column with link
253
213
  */
254
- function avatarColumn(
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
- cell: ({ row }) => {
267
- const src = row.getValue(accessorKey) as string | undefined
268
- const name = options?.nameKey ? (row.original as T)[options.nameKey] as string : undefined
269
-
270
- return h(UAvatar, {
271
- src,
272
- alt: name,
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 user column with avatar and name/email
228
+ * Create a link/URL column
282
229
  */
283
- function userColumn(
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
- id: 'user',
293
- header,
294
- cell: ({ row }) => {
295
- const original = row.original as T
296
- const avatar = config.avatarKey ? original[config.avatarKey] as string : undefined
297
- const name = original[config.nameKey] as string
298
- const email = config.emailKey ? original[config.emailKey] as string : undefined
299
-
300
- return h('div', { class: 'flex items-center gap-3' }, [
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 with dropdown menu
244
+ * Create an actions column
318
245
  */
319
- function actionsColumn(
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
- id: 'actions',
334
- header: () => h('div', { class: 'text-right' }, options?.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
- size: 60
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 a link column
263
+ * Create an editable column (supports inline editing)
371
264
  */
372
- function linkColumn(
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
- cell: ({ row }) => {
385
- const value = row.getValue(accessorKey)
386
- const href = getHref(row.original as T)
387
-
388
- return h('a', {
389
- href,
390
- target: options?.external ? '_blank' : undefined,
391
- rel: options?.external ? 'noopener noreferrer' : undefined,
392
- class: options?.class ?? 'text-primary-500 hover:text-primary-600 hover:underline'
393
- }, String(value ?? ''))
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 sortable column header
283
+ * Create a custom column with a render function
400
284
  */
401
- function sortableHeader(
402
- accessorKey: keyof T & string,
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: ({ column }) => {
408
- const isSorted = column.getIsSorted()
409
- return h(UButton, {
410
- color: 'neutral',
411
- variant: 'ghost',
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
- return {
424
- textColumn,
425
- dateColumn,
426
- dateTimeColumn,
427
- currencyColumn,
428
- numberColumn,
429
- badgeColumn,
430
- booleanColumn,
431
- avatarColumn,
432
- userColumn,
433
- actionsColumn,
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
- if (diffSecs < 60) return 'just now'
449
- if (diffMins < 60) return `${diffMins}m ago`
450
- if (diffHours < 24) return `${diffHours}h ago`
451
- if (diffDays < 7) return `${diffDays}d ago`
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 date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
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 }