@soft-stech/bootsman-ui-shadcn 2.0.20 → 2.0.22

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 (45) hide show
  1. package/dist/BuiPaginationCommon.vue_vue_type_script_setup_true_lang-BOuWIF4c.js +179 -0
  2. package/dist/BuiScrollArea.vue_vue_type_script_setup_true_lang-lyWD8KAT.js +144 -0
  3. package/dist/{BuiScrollBar.vue_vue_type_script_setup_true_lang-cV0od8j0.js → BuiScrollBar.vue_vue_type_script_setup_true_lang-BCvjzEmb.js} +4 -4
  4. package/dist/BuiTable.vue_vue_type_script_setup_true_lang-BQRl7YR1.js +37 -0
  5. package/dist/{BuiTableEmpty.vue_vue_type_script_setup_true_lang-Da4qHIWo.js → BuiTableEmpty.vue_vue_type_script_setup_true_lang-CuffOAuP.js} +1 -1
  6. package/dist/BuiTableRow.vue_vue_type_script_setup_true_lang-BQnadEa7.js +51 -0
  7. package/dist/components/input/index.js +22 -22
  8. package/dist/components/pagination/BuiPaginationCommon.js +1 -1
  9. package/dist/components/pagination/BuiPaginationCommon.vue.d.ts +4 -0
  10. package/dist/components/pagination/index.js +1 -1
  11. package/dist/components/scroll-area/BuiScrollArea.js +1 -1
  12. package/dist/components/scroll-area/BuiScrollArea.vue.d.ts +256 -2
  13. package/dist/components/scroll-area/BuiScrollBar.js +1 -1
  14. package/dist/components/scroll-area/index.js +2 -2
  15. package/dist/components/table/BuiDataTable.vue.d.ts +9 -4
  16. package/dist/components/table/BuiTable.js +1 -1
  17. package/dist/components/table/BuiTable.vue.d.ts +1487 -2
  18. package/dist/components/table/BuiTableEmpty.js +1 -1
  19. package/dist/components/table/BuiTableRow.js +1 -1
  20. package/dist/components/table/index.d.ts +1 -0
  21. package/dist/components/table/index.js +726 -663
  22. package/dist/index.js +6 -6
  23. package/dist/lib/useGlobalCursor.d.ts +4 -0
  24. package/dist/lib/useGlobalCursor.js +15 -0
  25. package/dist/lib/useResizeColumns.d.ts +3812 -0
  26. package/dist/lib/useResizeColumns.js +97 -79
  27. package/dist/style.css +1 -1
  28. package/package.json +1 -1
  29. package/src/components/input/BuiInput.vue +1 -1
  30. package/src/components/pagination/BuiPaginationCommon.vue +16 -4
  31. package/src/components/scroll-area/BuiScrollArea.vue +9 -2
  32. package/src/components/scroll-area/BuiScrollBar.vue +4 -4
  33. package/src/components/table/BuiDataTable.vue +152 -34
  34. package/src/components/table/BuiTable.vue +12 -3
  35. package/src/components/table/BuiTableRow.vue +6 -0
  36. package/src/components/table/index.ts +2 -0
  37. package/src/lib/useGlobalCursor.ts +17 -0
  38. package/src/lib/useResizeColumns.ts +151 -42
  39. package/src/stories/BuiDataTable.stories.ts +13 -0
  40. package/src/stories/components/BuiDataTableStory.vue +4 -4
  41. package/src/stories/components/BuiDataTableWithScrollStory.vue +292 -0
  42. package/dist/BuiPaginationCommon.vue_vue_type_script_setup_true_lang-DhSRYKth.js +0 -170
  43. package/dist/BuiScrollArea.vue_vue_type_script_setup_true_lang-XkIzRs-G.js +0 -141
  44. package/dist/BuiTable.vue_vue_type_script_setup_true_lang-Dd_dkcy4.js +0 -30
  45. package/dist/BuiTableRow.vue_vue_type_script_setup_true_lang-BJk8Yk1B.js +0 -54
@@ -1,20 +1,31 @@
1
- import { ref } from 'vue'
1
+ import { computed, ref } from 'vue'
2
2
  import BuiTableHeader from '@/components/table/BuiTableHeader.vue'
3
+ import BuiTable from '@/components/table/BuiTable.vue'
3
4
  import { useEventListener } from '@vueuse/core'
4
5
 
6
+ const MIN_CELL_WIDTH = 90
7
+ const LAST_CELL_EXTRA_SPACE = 56
8
+ const ACTIONS_CELL_MIN_WIDTH = 10
9
+
5
10
  export function useResizeColumns() {
6
11
  type CELL = {
7
- [key: string]: { cell: HTMLTableCellElement; initialWidth: number; minWidth: number }
12
+ [key: string]: {
13
+ cell: HTMLTableCellElement
14
+ initialWidth: number
15
+ minWidth: number
16
+ baseWidth: number
17
+ isLast: boolean
18
+ }
8
19
  }
9
20
  const isResizing = ref<boolean>(false)
10
21
  const resizingCellId = ref<string>('')
11
22
  const neighborCellId = ref<string>('')
12
23
  const cells = ref<CELL | undefined>(undefined)
13
- const MIN_CELL_WIDTH = 90
14
- const LAST_CELL_EXTRA_SPACE = 56
15
- const ACTIONS_CELL_MIN_WIDTH = 10
16
24
  const calculatedColumnSizing = ref<Record<string, number> | undefined>(undefined)
17
25
  const tableHeaderElement = ref<InstanceType<typeof BuiTableHeader> | null>(null)
26
+ const tableElement = ref<InstanceType<typeof BuiTable> | null>(null)
27
+ const initialTableWidth = ref<number>(0)
28
+ const minTableWidth = ref<number>(0)
18
29
  const unregisterMouseMove = ref<(() => void) | undefined>(undefined)
19
30
 
20
31
  const setProvidedCellWidths = (columnSizing: Record<string, number> | undefined) => {
@@ -25,31 +36,57 @@ export function useResizeColumns() {
25
36
  headerCells.forEach((cell) => {
26
37
  const cellId = getCellId(cell)
27
38
 
28
- if (columnSizing && columnSizing[cellId]) {
29
- cell.style.width = columnSizing[cellId] + 'px'
30
- }
39
+ cell.style.width = columnSizing && columnSizing[cellId] ? columnSizing[cellId] + 'px' : ''
31
40
  })
32
41
  }
33
42
  }
34
43
 
35
44
  const getCells = () => {
36
- if (tableHeaderElement.value && tableHeaderElement.value.headRef) {
45
+ if (
46
+ tableHeaderElement.value &&
47
+ tableHeaderElement.value.headRef &&
48
+ tableElement.value &&
49
+ tableElement.value?.tableRef
50
+ ) {
37
51
  const headerCells = [...tableHeaderElement.value.headRef.querySelectorAll('th')]
38
- const headerCellsWidths: CELL = headerCells.reduce((acc, cell) => {
52
+ const tableInitialWidth = getTableWidth()
53
+
54
+ tableElement.value.tableRef.style.width = 'min-content'
55
+
56
+ const headerCellsWidths: CELL = headerCells.reduce((acc, cell, index, array) => {
39
57
  const cellId = getCellId(cell)
58
+
40
59
  return {
41
60
  ...acc,
42
61
  [cellId]: {
43
62
  cell: cell,
44
- initialWidth: cell.offsetWidth,
63
+ isLast: index === array.length - 1,
64
+ initialWidth: Math.floor(cell.offsetWidth),
65
+ baseWidth: Math.floor(cell.offsetWidth),
45
66
  minWidth:
46
67
  cellId === 'actions'
47
68
  ? ACTIONS_CELL_MIN_WIDTH
48
- : Math.min(cell.offsetWidth, MIN_CELL_WIDTH)
69
+ : cell.offsetWidth < MIN_CELL_WIDTH
70
+ ? MIN_CELL_WIDTH
71
+ : cell.offsetWidth
49
72
  }
50
73
  }
51
74
  }, {})
52
75
 
76
+ tableElement.value.tableRef.style.width = ''
77
+
78
+ Object.values(headerCellsWidths).forEach((cellElement) => {
79
+ cellElement.baseWidth = Math.floor(cellElement.cell.offsetWidth)
80
+ })
81
+
82
+ setProvidedCellWidths(calculatedColumnSizing.value)
83
+ tableElement.value.tableRef.style.width =
84
+ (calculatedColumnSizing.value?.['table'] || tableInitialWidth) + 'px'
85
+
86
+ Object.values(headerCellsWidths).forEach((cellElement) => {
87
+ cellElement.initialWidth = Math.floor(cellElement.cell.offsetWidth)
88
+ })
89
+
53
90
  return headerCellsWidths
54
91
  }
55
92
 
@@ -109,6 +146,10 @@ export function useResizeColumns() {
109
146
  updatedColumnSizingValue[cell] = newWidth
110
147
  }
111
148
 
149
+ if (tableElement.value && tableElement.value?.tableRef) {
150
+ updatedColumnSizingValue['table'] = tableElement.value.tableRef.offsetWidth
151
+ }
152
+
112
153
  calculatedColumnSizing.value = updatedColumnSizingValue
113
154
  }
114
155
  }
@@ -121,16 +162,20 @@ export function useResizeColumns() {
121
162
  return 0
122
163
  }
123
164
 
165
+ const lastCell = computed(() => {
166
+ if (cells.value) {
167
+ return Object.values(cells.value).find((cell) => cell.isLast)
168
+ }
169
+
170
+ return undefined
171
+ })
172
+
124
173
  const getCellId = (cell: HTMLTableCellElement) => {
125
174
  return cell.id.split('_')[0]
126
175
  }
127
176
 
128
- const resizeCells = (
129
- cell: HTMLTableCellElement | null,
130
- neighborCell: HTMLTableCellElement | null,
131
- e: MouseEvent
132
- ) => {
133
- if (!cell || !neighborCell) {
177
+ const resizeCells = (cell: HTMLTableCellElement | null, e: MouseEvent) => {
178
+ if (!cell || !tableElement.value?.tableRef || !lastCell.value) {
134
179
  resizingCellId.value = ''
135
180
  neighborCellId.value = ''
136
181
 
@@ -140,7 +185,9 @@ export function useResizeColumns() {
140
185
  const movementX = e.movementX
141
186
  const direction: 'left' | 'right' = movementX < 0 ? 'left' : 'right'
142
187
  const newCellWidth = Math.floor(parseInt(cell.style.width)) + movementX
143
- const newNeighborCellWidth = Math.floor(parseInt(neighborCell.style.width)) - movementX
188
+ const newTableWidth =
189
+ Math.floor(parseInt(tableElement.value?.tableRef?.style.width)) + movementX
190
+ const newLastCellWidth = Math.floor(parseInt(lastCell.value.cell.style.width)) - movementX
144
191
 
145
192
  if (direction === 'left') {
146
193
  const min =
@@ -148,29 +195,35 @@ export function useResizeColumns() {
148
195
  ? cells.value[getCellId(cell)].minWidth
149
196
  : MIN_CELL_WIDTH
150
197
 
151
- if (newCellWidth <= min || !cell.hasAttribute('can-resize')) {
152
- const nextCell = cell.previousElementSibling as HTMLTableCellElement | null
198
+ if (newCellWidth >= min) {
199
+ cell.style.width = newCellWidth + 'px'
153
200
 
154
- resizeCells(nextCell, neighborCell, e)
201
+ if (newTableWidth >= minTableWidth.value) {
202
+ tableElement.value.tableRef.style.width = newTableWidth + 'px'
203
+ } else {
204
+ tableElement.value.tableRef.style.width = minTableWidth.value + 'px'
205
+ lastCell.value.cell.style.width = newLastCellWidth + 'px'
206
+ }
155
207
  } else {
156
- cell.style.width = newCellWidth + 'px'
157
- neighborCell.style.width = newNeighborCellWidth + 'px'
208
+ return
158
209
  }
159
210
  } else {
160
211
  const min =
161
- cells.value && cells.value[getCellId(neighborCell)]
162
- ? cells.value[getCellId(neighborCell)].minWidth +
163
- getLastCellOnTheRightExtraSpace(neighborCell)
212
+ cells.value && cells.value[getCellId(lastCell.value.cell)]
213
+ ? cells.value[getCellId(lastCell.value.cell)].minWidth +
214
+ getLastCellOnTheRightExtraSpace(lastCell.value.cell)
164
215
  : MIN_CELL_WIDTH
165
216
 
166
- if (newNeighborCellWidth <= min || !neighborCell.hasAttribute('can-resize')) {
167
- const nextNeighborCell = neighborCell.nextElementSibling as HTMLTableCellElement | null
168
-
169
- resizeCells(cell, nextNeighborCell, e)
217
+ if (
218
+ newLastCellWidth >= min &&
219
+ tableElement.value.tableRef.offsetWidth <= minTableWidth.value
220
+ ) {
221
+ lastCell.value.cell.style.width = newLastCellWidth + 'px'
170
222
  } else {
171
- cell.style.width = newCellWidth + 'px'
172
- neighborCell.style.width = newNeighborCellWidth + 'px'
223
+ tableElement.value.tableRef.style.width = newTableWidth + 'px'
173
224
  }
225
+
226
+ cell.style.width = newCellWidth + 'px'
174
227
  }
175
228
  }
176
229
 
@@ -179,27 +232,66 @@ export function useResizeColumns() {
179
232
 
180
233
  if (cells.value) {
181
234
  const resizingCell = cells.value[resizingCellId.value]?.cell
182
- const neighborCell = cells.value[neighborCellId.value]?.cell
183
235
 
184
- resizeCells(resizingCell, neighborCell, e)
236
+ resizeCells(resizingCell, e)
185
237
  }
186
238
  }
187
239
 
188
240
  const resetCells = () => {
189
- if (cells.value) {
190
- const updatedColumnSizingValue: Record<string, number> = {}
241
+ if (cells.value && tableElement.value && tableElement.value?.tableRef) {
242
+ tableElement.value.tableRef.style.width = ''
191
243
 
192
- for (const cell in cells.value) {
193
- const inititalWidth = cells.value[cell].initialWidth
244
+ calculatedColumnSizing.value = {}
245
+ setInitialColumnWidths()
246
+ }
247
+ }
194
248
 
195
- cells.value[cell].cell.style.width = inititalWidth + 'px'
196
- updatedColumnSizingValue[cell] = inititalWidth
249
+ const resetCell = (cellId: string) => {
250
+ if (
251
+ cells.value &&
252
+ calculatedColumnSizing.value &&
253
+ lastCell.value &&
254
+ tableElement.value &&
255
+ tableElement.value?.tableRef
256
+ ) {
257
+ const thisCellBaseWidth = cells.value[cellId].baseWidth
258
+ const thisCellCurrentWidth = calculatedColumnSizing.value[cellId]
259
+ const diff = thisCellBaseWidth - thisCellCurrentWidth
260
+
261
+ let newTableWidth = calculatedColumnSizing.value['table'] + diff
262
+
263
+ if (newTableWidth < minTableWidth.value) {
264
+ const tableWidthDiff = minTableWidth.value - newTableWidth
265
+ const newLastCellWidth =
266
+ calculatedColumnSizing.value[getCellId(lastCell.value.cell)] + tableWidthDiff
267
+
268
+ lastCell.value.cell.style.width = newLastCellWidth + 'px'
269
+ newTableWidth = minTableWidth.value
197
270
  }
198
271
 
199
- calculatedColumnSizing.value = updatedColumnSizingValue
272
+ cells.value[cellId].cell.style.width = thisCellBaseWidth + 'px'
273
+ tableElement.value.tableRef.style.width = newTableWidth + 'px'
274
+ calculatedColumnSizing.value[cellId] = thisCellBaseWidth
275
+ calculatedColumnSizing.value['table'] = newTableWidth
200
276
  }
201
277
  }
202
278
 
279
+ const getTableWrapperWidth = () => {
280
+ if (tableElement.value && tableElement.value.scrollAreaElementRef?.tableWrapperRef) {
281
+ return tableElement.value.scrollAreaElementRef?.tableWrapperRef.$el.offsetWidth - 3
282
+ }
283
+
284
+ return 0
285
+ }
286
+
287
+ const getTableWidth = () => {
288
+ if (tableElement.value && tableElement.value?.tableRef) {
289
+ return tableElement.value.tableRef.offsetWidth
290
+ }
291
+
292
+ return 0
293
+ }
294
+
203
295
  const setInitialColumnWidths = () => {
204
296
  cells.value = getCells()
205
297
 
@@ -211,21 +303,38 @@ export function useResizeColumns() {
211
303
  cells.value[cell].cell.style.width = cells.value[cell].initialWidth + 'px'
212
304
  }
213
305
 
306
+ cells.value[cell].cell.style.minWidth = cells.value[cell].minWidth + 'px'
307
+
214
308
  updatedColumnSizingValue[cell] = cells.value[cell].cell.offsetWidth
215
309
  }
216
310
 
311
+ if (tableElement.value && tableElement.value?.tableRef) {
312
+ const tableOffsetWidth = getTableWidth()
313
+
314
+ if (calculatedColumnSizing.value?.['table'] && !tableElement.value.tableRef.style.width) {
315
+ tableElement.value.tableRef.style.width = calculatedColumnSizing.value['table'] + 'px'
316
+ } else {
317
+ initialTableWidth.value = tableOffsetWidth
318
+ tableElement.value.tableRef.style.width = tableOffsetWidth + 'px'
319
+ updatedColumnSizingValue['table'] = tableOffsetWidth
320
+ }
321
+ }
322
+
323
+ minTableWidth.value = getTableWrapperWidth()
217
324
  calculatedColumnSizing.value = updatedColumnSizingValue
218
325
  }
219
326
  }
220
327
 
221
328
  return {
222
329
  cells,
330
+ tableElement,
223
331
  tableHeaderElement,
224
332
  calculatedColumnSizing,
225
333
  isResizing,
226
334
  resizingCellId,
227
335
  handleResizeControlMouseDown,
228
336
  handleResizeControlMouseUp,
337
+ resetCell,
229
338
  resetCells,
230
339
  setInitialColumnWidths,
231
340
  setProvidedCellWidths,
@@ -1,5 +1,6 @@
1
1
  import { BuiDataTable } from '@/components/table'
2
2
  import BuiDataTableStory from '@/stories/components/BuiDataTableStory.vue'
3
+ import BuiDataTableWithScrollStory from '@/stories/components/BuiDataTableWithScrollStory.vue'
3
4
  import type { Meta, StoryObj } from '@storybook/vue3-vite'
4
5
 
5
6
  const meta = {
@@ -24,3 +25,15 @@ export const Default: Story = {
24
25
  template: `<BuiDataTableStory v-bind="args" />`
25
26
  })
26
27
  }
28
+
29
+ export const WithScroll: Story = {
30
+ // @ts-expect-error no need to describe all args, see BuiDataTableStory
31
+ args: {},
32
+ render: (args) => ({
33
+ components: { BuiDataTableWithScrollStory },
34
+ setup() {
35
+ return { args }
36
+ },
37
+ template: `<BuiDataTableWithScrollStory v-bind="args" />`
38
+ })
39
+ }
@@ -3,7 +3,6 @@ import { BuiDataTable } from '@/components/table'
3
3
  import RowActionsMenuContent from './ActionsMenuContent.vue'
4
4
  import type {
5
5
  ColumnDef,
6
- PaginationState,
7
6
  Row,
8
7
  RowSelectionState,
9
8
  VisibilityState,
@@ -25,6 +24,7 @@ import { BuiCheckbox } from '@/components/checkbox'
25
24
  import { tableColumnSortCommon } from '@/lib/utils'
26
25
  import { BuiButton } from '@/components/button'
27
26
  import { BuiTabs, BuiTabsList, BuiTabsTrigger } from '@/components/tabs'
27
+ import type { PaginationAutoState } from '@/components/table/BuiDataTable.vue'
28
28
 
29
29
  const taskSchema = z.object({
30
30
  id: z.string(),
@@ -102,7 +102,7 @@ function onGroupAction(group: string | number, action: string) {
102
102
 
103
103
  type TaskSortingState = { id: keyof Task; desc: boolean }
104
104
  const sorting = ref<TaskSortingState[]>([{ id: 'id', desc: false }])
105
- const pagination = ref<PaginationState>({
105
+ const pagination = ref<PaginationAutoState>({
106
106
  pageIndex: 0,
107
107
  pageSize: 10
108
108
  })
@@ -115,7 +115,7 @@ function updateSelection(val?: RowSelectionState) {
115
115
  }
116
116
 
117
117
  const columnVisibility = ref<VisibilityState>({ hiddenColumn: false })
118
- const columnSizing = ref<Record<string, number>>({ title: 450 })
118
+ const columnSizing = ref<Record<string, number>>({})
119
119
  const columnOrder = ref<ColumnOrderState>()
120
120
 
121
121
  type GroupBy = 'none' | 'status' | 'priority'
@@ -202,7 +202,6 @@ function groupName(group: string | number) {
202
202
  v-model:column-order="columnOrder"
203
203
  @update:selection="updateSelection"
204
204
  :total-items="totalItems"
205
- class="caption-top"
206
205
  :manualPagination="false"
207
206
  :getRowId="(row) => row.id"
208
207
  :groupBy="groupBy === 'none' ? undefined : groupBy"
@@ -212,6 +211,7 @@ function groupName(group: string | number) {
212
211
  :enable-group-folding="true"
213
212
  :pagination-translations="{
214
213
  itemsPerPage: 'Tasks per page',
214
+ itemsPerPageAuto: 'Auto',
215
215
  page: 'Page',
216
216
  of: 'of'
217
217
  }"
@@ -0,0 +1,292 @@
1
+ <script setup lang="ts">
2
+ import { BuiDataTable } from '@/components/table'
3
+ import RowActionsMenuContent from './ActionsMenuContent.vue'
4
+ import type {
5
+ ColumnDef,
6
+ PaginationState,
7
+ Row,
8
+ RowSelectionState,
9
+ VisibilityState,
10
+ ColumnOrderState
11
+ } from '@tanstack/vue-table'
12
+ import { sort, type ISortByObjectSorter } from 'fast-sort'
13
+ import {
14
+ AlignJustifyIcon,
15
+ ArrowUpNarrowWideIcon,
16
+ FolderIcon,
17
+ SignalHighIcon,
18
+ SignalMediumIcon,
19
+ SignalLowIcon
20
+ } from 'lucide-vue-next'
21
+ import { computed, h, ref, withModifiers } from 'vue'
22
+ import { z } from 'zod'
23
+ import tasks from '@/stories/data/tasks.json'
24
+ import { BuiCheckbox } from '@/components/checkbox'
25
+ import { tableColumnSortCommon } from '@/lib/utils'
26
+ import { BuiButton } from '@/components/button'
27
+ import { BuiTabs, BuiTabsList, BuiTabsTrigger } from '@/components/tabs'
28
+
29
+ const taskSchema = z.object({
30
+ id: z.string(),
31
+ title: z.string(),
32
+ status: z.string().nullable().optional(),
33
+ label: z.string(),
34
+ priority: z.string(),
35
+ errorMessage: z.string().optional(),
36
+ age: z.string().optional()
37
+ })
38
+ type Task = z.infer<typeof taskSchema>
39
+
40
+ const columns: ColumnDef<Task>[] = [
41
+ {
42
+ id: 'id',
43
+ accessorKey: 'id',
44
+ header: ({ table, column }) => {
45
+ return h('div', { class: 'flex items-center gap-2' }, [
46
+ h(BuiCheckbox, {
47
+ modelValue: table.getIsSomePageRowsSelected()
48
+ ? 'indeterminate'
49
+ : table.getIsAllPageRowsSelected(),
50
+ 'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
51
+ table.getIsSomePageRowsSelected()
52
+ ? table.toggleAllPageRowsSelected(false)
53
+ : table.toggleAllPageRowsSelected(!!value),
54
+ ariaLabel: 'Select row',
55
+ onClick: withModifiers(() => {}, ['stop'])
56
+ }),
57
+ tableColumnSortCommon(column, 'ID')
58
+ ])
59
+ },
60
+ cell: ({ row }) =>
61
+ h('div', { class: 'flex items-center gap-2' }, [
62
+ h(BuiCheckbox, {
63
+ modelValue: row.getIsSelected(),
64
+ 'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
65
+ ariaLabel: 'Select row'
66
+ }),
67
+ `${row.getValue('id')}`
68
+ ]),
69
+ enableHiding: false,
70
+ meta: { title: 'ID', pinLeft: true }
71
+ },
72
+ {
73
+ accessorKey: 'title',
74
+ header: 'Title',
75
+ enableSorting: false
76
+ },
77
+ {
78
+ accessorKey: 'status',
79
+ header: ({ column }) => tableColumnSortCommon(column, 'Очень длинный заголовок для статуса'),
80
+ meta: { title: 'Статус таска' }
81
+ },
82
+ {
83
+ accessorKey: 'priority',
84
+ header: ({ column }) => tableColumnSortCommon(column, 'Priorities')
85
+ },
86
+ {
87
+ accessorKey: 'age',
88
+ header: ({ column }) => tableColumnSortCommon(column, 'Age')
89
+ },
90
+ { id: 'hiddenColumn', header: 'Hidden Column', cell: 'secret info' }
91
+ ]
92
+ const data = ref<Task[]>(tasks)
93
+
94
+ function onRowAction(row: Task, action: string) {
95
+ const str = `${action}: ${row.id}`
96
+ alert(str)
97
+ }
98
+ function onGroupAction(group: string | number, action: string) {
99
+ const str = `${action}: ${group}`
100
+ alert(str)
101
+ }
102
+
103
+ type TaskSortingState = { id: keyof Task; desc: boolean }
104
+ const sorting = ref<TaskSortingState[]>([{ id: 'id', desc: false }])
105
+ const pagination = ref<PaginationState>({
106
+ pageIndex: 0,
107
+ pageSize: 10
108
+ })
109
+ const totalItems = tasks.length
110
+
111
+ const selection = ref<RowSelectionState | undefined>({})
112
+ function updateSelection(val?: RowSelectionState) {
113
+ console.log('selection was changed', val)
114
+ selection.value = val
115
+ }
116
+
117
+ const columnVisibility = ref<VisibilityState>({ hiddenColumn: false })
118
+ const columnSizing = ref<Record<string, number>>({ title: 450 })
119
+ const columnOrder = ref<ColumnOrderState>()
120
+
121
+ type GroupBy = 'none' | 'status' | 'priority'
122
+ const groupBy = ref<GroupBy>('none')
123
+ const groupLabels = {
124
+ status: ['Status', 'Not in any status'],
125
+ priority: ['Priority', 'Not in any priorities']
126
+ }
127
+
128
+ const sortedData = computed(() => {
129
+ const sortDirection = (sorting.value[0].desc ? 'desc' : 'asc') as 'asc'
130
+ const sortColumn = sorting.value[0].id
131
+ const groupByStr = groupBy.value
132
+
133
+ const sortBy: ISortByObjectSorter<Task> | ISortByObjectSorter<Task>[] = [
134
+ {
135
+ [sortDirection]: sortColumn
136
+ }
137
+ ]
138
+
139
+ // sort by grouping column first, but not when manually sorting by it
140
+ if (groupByStr !== 'none' && sortColumn !== groupByStr) {
141
+ sortBy.unshift({
142
+ asc: groupByStr
143
+ })
144
+ }
145
+
146
+ // sort by ID when possible
147
+ if (sortColumn !== 'id') {
148
+ sortBy.push({
149
+ asc: 'id'
150
+ })
151
+ }
152
+
153
+ return sort(data.value).by([...sortBy])
154
+ })
155
+
156
+ function renderSubComponent(row: Row<Task>) {
157
+ if (row.original.errorMessage) {
158
+ return () => h('span', { style: 'color: red' }, `Subrow: ${row.original.errorMessage}`)
159
+ } else {
160
+ return undefined
161
+ }
162
+ }
163
+
164
+ function deleteRow() {
165
+ data.value = data.value.map((a) => a)
166
+ }
167
+ function updateRows() {
168
+ data.value.shift()
169
+ }
170
+
171
+ function groupName(group: string | number) {
172
+ if (groupBy.value === 'priority') {
173
+ if (group === 'high') {
174
+ return () => h(SignalHighIcon, { class: 'size-4 inline-block' })
175
+ }
176
+
177
+ if (group === 'medium') {
178
+ return () => h(SignalMediumIcon, { class: 'size-4 inline-block' })
179
+ }
180
+
181
+ if (group === 'low') {
182
+ return () => h(SignalLowIcon, { class: 'size-4 inline-block' })
183
+ }
184
+
185
+ return () => group
186
+ }
187
+
188
+ return () => group
189
+ }
190
+ </script>
191
+
192
+ <template>
193
+ <Story title="BuiDataTable" autoPropsDisabled :layout="{ type: 'grid', width: '95%' }">
194
+ <Variant key="variant" title="Sorting, Pagination, Grouping, Subrow">
195
+ <div class="page-wrapper">
196
+ <div class="table-wrapper">
197
+ <BuiDataTable
198
+ :columns="columns"
199
+ :data="sortedData"
200
+ v-model:sorting="sorting"
201
+ v-model:pagination="pagination"
202
+ v-model:column-visibility="columnVisibility"
203
+ v-model:column-sizing="columnSizing"
204
+ v-model:column-order="columnOrder"
205
+ @update:selection="updateSelection"
206
+ :total-items="totalItems"
207
+ class="caption-top"
208
+ :manualPagination="false"
209
+ :getRowId="(row) => row.id"
210
+ :groupBy="groupBy === 'none' ? undefined : groupBy"
211
+ :groupLabels="groupLabels"
212
+ :renderSubComponent="renderSubComponent"
213
+ :freeze-header="true"
214
+ enable-column-list-control
215
+ :enable-group-folding="true"
216
+ :pagination-translations="{
217
+ itemsPerPage: 'Tasks per page',
218
+ itemsPerPageAuto: 'Auto',
219
+ page: 'Page',
220
+ of: 'of'
221
+ }"
222
+ >
223
+ <template #caption="{ table }">
224
+ <div class="flex h-fit items-center justify-between">
225
+ <div class="flex h-full flex-row items-center gap-3">
226
+ <BuiButton variant="outline">Download YAML</BuiButton>
227
+ <BuiButton variant="outline" @click="updateRows"> Delete row </BuiButton>
228
+ <BuiButton variant="outline" @click="deleteRow"> Update rows </BuiButton>
229
+ </div>
230
+
231
+ <div class="flex h-full flex-row items-center gap-3">
232
+ <BuiTabs v-model="groupBy">
233
+ <BuiTabsList class="grid w-full grid-cols-3" variant="default">
234
+ <BuiTabsTrigger value="none" variant="default">
235
+ <AlignJustifyIcon :size="14" />
236
+ </BuiTabsTrigger>
237
+ <BuiTabsTrigger value="status" variant="default">
238
+ <FolderIcon :size="14" />
239
+ </BuiTabsTrigger>
240
+ <BuiTabsTrigger value="priority" variant="default">
241
+ <ArrowUpNarrowWideIcon :size="14" />
242
+ </BuiTabsTrigger>
243
+ </BuiTabsList>
244
+ </BuiTabs>
245
+
246
+ <span>
247
+ {{ table.getFilteredSelectedRowModel().rows?.length }} of
248
+ {{ table.getFilteredRowModel().rows?.length }} row(s) selected
249
+ </span>
250
+ </div>
251
+ </div>
252
+ </template>
253
+ <template #nodata>No data</template>
254
+ <template #groupByRow="{ group }"> Optional slot for: `{{ group }}` </template>
255
+ <template #groupName="{ group }">
256
+ <component :is="groupName(group)"></component>
257
+ </template>
258
+ <template #groupActions="{ group }">
259
+ <RowActionsMenuContent
260
+ :actions="['Group action']"
261
+ @select="(action) => onGroupAction(group, action)"
262
+ />
263
+ </template>
264
+ <template #rowActions="{ row }">
265
+ <RowActionsMenuContent
266
+ :actions="['action 1', 'action 2']"
267
+ @select="(action) => onRowAction(row, action)"
268
+ />
269
+ </template>
270
+ <template #numberOfItems>{{ data.length }} tasks</template>
271
+ </BuiDataTable>
272
+ </div>
273
+ </div>
274
+ </Variant>
275
+ </Story>
276
+ </template>
277
+
278
+ <style scoped>
279
+ .page-wrapper {
280
+ height: 95vh;
281
+ flex-direction: column;
282
+ flex-grow: 1;
283
+ display: flex;
284
+ overflow: hidden auto;
285
+ }
286
+
287
+ .table-wrapper {
288
+ height: 100%;
289
+ flex-direction: column;
290
+ display: flex;
291
+ }
292
+ </style>