@indielayer/ui 1.15.3 → 1.17.0

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 (129) hide show
  1. package/docs/components/menu/DocsMenu.vue +3 -0
  2. package/docs/pages/component/infiniteLoader/composable.vue +168 -0
  3. package/docs/pages/component/infiniteLoader/index.vue +36 -0
  4. package/docs/pages/component/infiniteLoader/usage.vue +161 -0
  5. package/docs/pages/component/table/usage.vue +13 -0
  6. package/docs/pages/component/virtualGrid/index.vue +29 -0
  7. package/docs/pages/component/virtualGrid/usage.vue +20 -0
  8. package/docs/pages/component/virtualList/dynamicHeight.vue +75 -0
  9. package/docs/pages/component/virtualList/index.vue +36 -0
  10. package/docs/pages/component/virtualList/usage.vue +17 -0
  11. package/docs/search/components.json +1 -1
  12. package/lib/components/select/Select.vue.js +35 -35
  13. package/lib/components/table/Table.vue.d.ts +9 -0
  14. package/lib/components/table/Table.vue.js +190 -160
  15. package/lib/components/tooltip/Tooltip.vue.js +64 -52
  16. package/lib/composables/useVirtualList.d.ts +1 -1
  17. package/lib/index.d.ts +1 -0
  18. package/lib/index.js +88 -76
  19. package/lib/index.umd.js +4 -4
  20. package/lib/install.js +15 -7
  21. package/lib/version.d.ts +1 -1
  22. package/lib/version.js +1 -1
  23. package/lib/virtual/components/infiniteLoader/InfiniteLoader.test.d.ts +1 -0
  24. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.d.ts +49 -0
  25. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.js +21 -0
  26. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue2.js +4 -0
  27. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.d.ts +185 -0
  28. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.js +241 -0
  29. package/lib/virtual/components/virtualGrid/VirtualGrid.vue2.js +4 -0
  30. package/lib/virtual/components/virtualGrid/types.d.ts +138 -0
  31. package/lib/virtual/components/virtualList/VirtualList.test.d.ts +1 -0
  32. package/lib/virtual/components/virtualList/VirtualList.vue.d.ts +135 -0
  33. package/lib/virtual/components/virtualList/VirtualList.vue.js +157 -0
  34. package/lib/virtual/components/virtualList/VirtualList.vue2.js +4 -0
  35. package/lib/virtual/components/virtualList/isDynamicRowHeight.d.ts +2 -0
  36. package/lib/virtual/components/virtualList/isDynamicRowHeight.js +6 -0
  37. package/lib/virtual/components/virtualList/types.d.ts +115 -0
  38. package/lib/virtual/components/virtualList/useDynamicRowHeight.d.ts +7 -0
  39. package/lib/virtual/components/virtualList/useDynamicRowHeight.js +69 -0
  40. package/lib/virtual/components/virtualList/useDynamicRowHeight.test.d.ts +1 -0
  41. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.d.ts +8 -0
  42. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.js +41 -0
  43. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.test.d.ts +1 -0
  44. package/lib/virtual/composables/infinite-loader/types.d.ts +30 -0
  45. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.d.ts +6 -0
  46. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.js +42 -0
  47. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.test.d.ts +1 -0
  48. package/lib/virtual/core/createCachedBounds.d.ts +6 -0
  49. package/lib/virtual/core/createCachedBounds.js +55 -0
  50. package/lib/virtual/core/getEstimatedSize.d.ts +6 -0
  51. package/lib/virtual/core/getEstimatedSize.js +22 -0
  52. package/lib/virtual/core/getOffsetForIndex.d.ts +11 -0
  53. package/lib/virtual/core/getOffsetForIndex.js +40 -0
  54. package/lib/virtual/core/getStartStopIndices.d.ts +13 -0
  55. package/lib/virtual/core/getStartStopIndices.js +31 -0
  56. package/lib/virtual/core/getStartStopIndices.test.d.ts +1 -0
  57. package/lib/virtual/core/types.d.ts +11 -0
  58. package/lib/virtual/core/useCachedBounds.d.ts +7 -0
  59. package/lib/virtual/core/useCachedBounds.js +18 -0
  60. package/lib/virtual/core/useIsRtl.d.ts +2 -0
  61. package/lib/virtual/core/useIsRtl.js +15 -0
  62. package/lib/virtual/core/useItemSize.d.ts +5 -0
  63. package/lib/virtual/core/useItemSize.js +27 -0
  64. package/lib/virtual/core/useVirtualizer.d.ts +33 -0
  65. package/lib/virtual/core/useVirtualizer.js +171 -0
  66. package/lib/virtual/index.d.ts +9 -0
  67. package/lib/virtual/test-utils/mockResizeObserver.d.ts +15 -0
  68. package/lib/virtual/types.d.ts +2 -0
  69. package/lib/virtual/utils/adjustScrollOffsetForRtl.d.ts +7 -0
  70. package/lib/virtual/utils/adjustScrollOffsetForRtl.js +24 -0
  71. package/lib/virtual/utils/areArraysEqual.d.ts +1 -0
  72. package/lib/virtual/utils/assert.d.ts +1 -0
  73. package/lib/virtual/utils/assert.js +7 -0
  74. package/lib/virtual/utils/getRTLOffsetType.d.ts +2 -0
  75. package/lib/virtual/utils/getRTLOffsetType.js +13 -0
  76. package/lib/virtual/utils/getScrollbarSize.d.ts +2 -0
  77. package/lib/virtual/utils/getScrollbarSize.js +11 -0
  78. package/lib/virtual/utils/isRtl.d.ts +1 -0
  79. package/lib/virtual/utils/isRtl.js +12 -0
  80. package/lib/virtual/utils/parseNumericStyleValue.d.ts +2 -0
  81. package/lib/virtual/utils/parseNumericStyleValue.js +15 -0
  82. package/lib/virtual/utils/shallowCompare.d.ts +1 -0
  83. package/lib/virtual/utils/shallowCompare.js +14 -0
  84. package/package.json +1 -1
  85. package/src/components/select/Select.vue +3 -2
  86. package/src/components/table/Table.vue +23 -2
  87. package/src/components/tooltip/Tooltip.vue +25 -5
  88. package/src/composables/useVirtualList.ts +1 -1
  89. package/src/index.ts +1 -0
  90. package/src/install.ts +9 -3
  91. package/src/version.ts +1 -1
  92. package/src/virtual/README.md +285 -0
  93. package/src/virtual/components/infiniteLoader/InfiniteLoader.test.ts +96 -0
  94. package/src/virtual/components/infiniteLoader/InfiniteLoader.vue +18 -0
  95. package/src/virtual/components/virtualGrid/VirtualGrid.vue +322 -0
  96. package/src/virtual/components/virtualGrid/types.ts +160 -0
  97. package/src/virtual/components/virtualList/VirtualList.test.ts +47 -0
  98. package/src/virtual/components/virtualList/VirtualList.vue +233 -0
  99. package/src/virtual/components/virtualList/isDynamicRowHeight.ts +13 -0
  100. package/src/virtual/components/virtualList/types.ts +127 -0
  101. package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +183 -0
  102. package/src/virtual/components/virtualList/useDynamicRowHeight.ts +147 -0
  103. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.test.ts +141 -0
  104. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.ts +82 -0
  105. package/src/virtual/composables/infinite-loader/types.ts +36 -0
  106. package/src/virtual/composables/infinite-loader/useInfiniteLoader.test.ts +236 -0
  107. package/src/virtual/composables/infinite-loader/useInfiniteLoader.ts +88 -0
  108. package/src/virtual/core/createCachedBounds.ts +72 -0
  109. package/src/virtual/core/getEstimatedSize.ts +29 -0
  110. package/src/virtual/core/getOffsetForIndex.ts +90 -0
  111. package/src/virtual/core/getStartStopIndices.test.ts +45 -0
  112. package/src/virtual/core/getStartStopIndices.ts +71 -0
  113. package/src/virtual/core/types.ts +17 -0
  114. package/src/virtual/core/useCachedBounds.ts +21 -0
  115. package/src/virtual/core/useIsRtl.ts +25 -0
  116. package/src/virtual/core/useItemSize.ts +34 -0
  117. package/src/virtual/core/useVirtualizer.ts +294 -0
  118. package/src/virtual/index.ts +25 -0
  119. package/src/virtual/test-utils/mockResizeObserver.ts +162 -0
  120. package/src/virtual/types.ts +3 -0
  121. package/src/virtual/utils/adjustScrollOffsetForRtl.ts +37 -0
  122. package/src/virtual/utils/areArraysEqual.ts +13 -0
  123. package/src/virtual/utils/assert.ts +10 -0
  124. package/src/virtual/utils/getRTLOffsetType.ts +51 -0
  125. package/src/virtual/utils/getScrollbarSize.ts +24 -0
  126. package/src/virtual/utils/isRtl.ts +13 -0
  127. package/src/virtual/utils/parseNumericStyleValue.ts +19 -0
  128. package/src/virtual/utils/shallowCompare.ts +29 -0
  129. package/volar.d.ts +3 -0
@@ -69,8 +69,11 @@ const components = [
69
69
  collapseIcon: 'chevron-down',
70
70
  expanded: true,
71
71
  items: [
72
+ { to: '/component/infiniteLoader', label: 'Infinite Loader' },
72
73
  { to: '/component/scroll', label: 'Scroll' },
73
74
  { to: '/component/spacer', label: 'Spacer' },
75
+ { to: '/component/virtualGrid', label: 'Virtual Grid' },
76
+ { to: '/component/virtualList', label: 'Virtual List' },
74
77
  ],
75
78
  },
76
79
  ]
@@ -0,0 +1,168 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from 'vue'
3
+ import { useInfiniteLoader } from '@indielayer/ui'
4
+
5
+ // Infinite loader example
6
+ const infiniteItems = ref<string[]>([])
7
+ const hasMoreData = ref(true)
8
+ const isLoading = ref(false)
9
+ const highestLoadedIndex = ref(-1)
10
+ const apiCursor = ref<string | null>(null) // Cursor from last API call
11
+
12
+ // Track the actual loaded count
13
+ const loadedCount = computed(() => {
14
+ return infiniteItems.value.filter((item) => item !== undefined).length
15
+ })
16
+
17
+ const isRowLoaded = (index: number) => {
18
+ // If loading, pretend ALL rows are loaded to prevent useInfiniteLoader
19
+ // from marking new rows as pending while a request is in progress
20
+ if (isLoading.value) {
21
+ return true
22
+ }
23
+
24
+ // The last row is the loading indicator when we have more data
25
+ const isLoadingRow = hasMoreData.value && index === infiniteItems.value.length
26
+
27
+ if (isLoadingRow) {
28
+ return false // Not loaded yet, trigger loading
29
+ }
30
+
31
+ return infiniteItems.value[index] !== undefined
32
+ }
33
+
34
+ // Helper to get item or loading indicator
35
+ const getItem = (index: number) => {
36
+ // If this is the loading indicator row
37
+ if (hasMoreData.value && index === infiniteItems.value.length) {
38
+ return isLoading.value ? '⏳ Loading more...' : '↓ Scroll for more'
39
+ }
40
+
41
+ return infiniteItems.value[index]
42
+ }
43
+
44
+ const loadMoreRows = async (startIndex: number, stopIndex: number) => {
45
+ // Block all new requests if one is already in progress
46
+ if (isLoading.value) {
47
+ console.log(`[loadMoreRows] Already loading, rejecting request for ${startIndex}-${stopIndex}`)
48
+
49
+ return
50
+ }
51
+
52
+ isLoading.value = true
53
+ const currentLength = infiniteItems.value.length
54
+
55
+ console.log(`[loadMoreRows] Loading batch of 50 items, cursor: ${apiCursor.value}, current items: ${currentLength}`)
56
+
57
+ try {
58
+ // Simulate API call with cursor
59
+ // In real implementation: const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
60
+ await new Promise((resolve) => setTimeout(resolve, 1000))
61
+
62
+ // In a real implementation, you'd use the cursor and response data:
63
+ // const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
64
+ // const { items, nextCursor, hasMore } = await response.json();
65
+ // apiCursor.value = nextCursor;
66
+ // hasMoreData.value = hasMore;
67
+ //
68
+ // Then append the items from the API:
69
+ // const newItems = [...infiniteItems.value, ...items];
70
+ // infiniteItems.value = newItems;
71
+
72
+ // Simulate API response: generate 50 items
73
+ const batchSize = 50
74
+ const itemsToAdd = []
75
+
76
+ for (let i = 0; i < batchSize; i++) {
77
+ const itemIndex = currentLength + i
78
+
79
+ itemsToAdd.push(`Item ${itemIndex + 1}`)
80
+ if (itemIndex > highestLoadedIndex.value) {
81
+ highestLoadedIndex.value = itemIndex
82
+ }
83
+ }
84
+
85
+ // Simulate updating cursor based on response
86
+ apiCursor.value = `cursor_after_${currentLength + batchSize - 1}`
87
+
88
+ // Append all new items at once
89
+ infiniteItems.value = [...infiniteItems.value, ...itemsToAdd]
90
+
91
+ console.log(`[loadMoreRows] Loaded ${batchSize} items, total: ${infiniteItems.value.length}, cursor: ${apiCursor.value}`)
92
+
93
+ // Check if we've reached the limit (simulate end of data)
94
+ if (infiniteItems.value.length >= 500) {
95
+ hasMoreData.value = false
96
+ console.log('[loadMoreRows] Reached limit, no more data to load')
97
+ }
98
+ } catch (error) {
99
+ console.error('[loadMoreRows] Error loading items:', error)
100
+ } finally {
101
+ isLoading.value = false
102
+ }
103
+ }
104
+
105
+ const infiniteRowCount = computed(() => {
106
+ const itemsLength = infiniteItems.value.length
107
+
108
+ if (!hasMoreData.value) {
109
+ // No more data, show only loaded items
110
+ return itemsLength
111
+ }
112
+
113
+ // Show loaded items + 1 loading indicator row
114
+ return itemsLength + 1
115
+ })
116
+
117
+ const { onRowsRendered } = useInfiniteLoader(
118
+ computed(() => {
119
+ const rc = infiniteRowCount.value
120
+
121
+ console.log(`[useInfiniteLoader] Props updated - rowCount: ${rc}, loaded: ${loadedCount.value}`)
122
+
123
+ return {
124
+ isRowLoaded,
125
+ loadMoreRows,
126
+ rowCount: rc,
127
+ minimumBatchSize: 50, // Load 50 items per batch
128
+ threshold: 10, // Start loading when within 10 rows of needing data
129
+ }
130
+ }),
131
+ )
132
+
133
+ // Load first batch of items on mount or when switching to infinite tab
134
+ const loadInitialItems = async () => {
135
+ if (infiniteItems.value.length === 0) {
136
+ await loadMoreRows(0, 49) // Indices are ignored, always loads 50 items
137
+ }
138
+ }
139
+
140
+ onMounted(() => {
141
+ loadInitialItems()
142
+ })
143
+ </script>
144
+
145
+ <template>
146
+ <p class="info">
147
+ Loaded {{ loadedCount }} of {{ infiniteRowCount }} items
148
+ <span v-if="isLoading" class="loading"> (Loading...)</span>
149
+ </p>
150
+ <x-virtual-list
151
+ :row-count="infiniteRowCount"
152
+ :row-height="50"
153
+ class="h-96 border border-gray-200 rounded-md bg-white"
154
+ :on-rows-rendered="onRowsRendered"
155
+ >
156
+ <template #row="{ index, style }">
157
+ <div
158
+ :style="style"
159
+ :class="[
160
+ 'h-12 flex items-center justify-center border-b border-gray-200',
161
+ { 'loading-indicator': index === infiniteItems.length && hasMoreData }
162
+ ]"
163
+ >
164
+ {{ getItem(index) }}
165
+ </div>
166
+ </template>
167
+ </x-virtual-list>
168
+ </template>
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ import { XInfiniteLoader } from '@indielayer/ui'
3
+ import UsageDemoCode from './usage.vue?raw'
4
+ import UsageDemo from './usage.vue'
5
+ import ComposableDemoCode from './composable.vue?raw'
6
+ import ComposableDemo from './composable.vue'
7
+
8
+ const title = 'InfiniteLoader'
9
+ const description = 'InfiniteLoader is a component that allows you to load data on demand as users scroll through a list.'
10
+ const components = [XInfiniteLoader]
11
+ const demos = [{
12
+ name: 'Usage',
13
+ description: '',
14
+ code: UsageDemoCode,
15
+ component: UsageDemo,
16
+ }, {
17
+ name: 'Composable',
18
+ description: '',
19
+ code: ComposableDemoCode,
20
+ component: ComposableDemo,
21
+ }]
22
+ const back = ''
23
+ const next = ''
24
+ </script>
25
+
26
+ <template>
27
+ <document-page
28
+ github="https://github.com/indielayer/ui/blob/main/packages/ui/docs/pages/component/infiniteLoader"
29
+ :title="title"
30
+ :description="description"
31
+ :components="components"
32
+ :demos="demos"
33
+ :back="back"
34
+ :next="next"
35
+ />
36
+ </template>
@@ -0,0 +1,161 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from 'vue'
3
+
4
+ // Infinite loader example
5
+ const infiniteItems = ref<string[]>([])
6
+ const hasMoreData = ref(true)
7
+ const isLoading = ref(false)
8
+ const highestLoadedIndex = ref(-1)
9
+ const apiCursor = ref<string | null>(null) // Cursor from last API call
10
+
11
+ // Track the actual loaded count
12
+ const loadedCount = computed(() => {
13
+ return infiniteItems.value.filter((item) => item !== undefined).length
14
+ })
15
+
16
+ const isRowLoaded = (index: number) => {
17
+ // If loading, pretend ALL rows are loaded to prevent useInfiniteLoader
18
+ // from marking new rows as pending while a request is in progress
19
+ if (isLoading.value) {
20
+ return true
21
+ }
22
+
23
+ // The last row is the loading indicator when we have more data
24
+ const isLoadingRow = hasMoreData.value && index === infiniteItems.value.length
25
+
26
+ if (isLoadingRow) {
27
+ return false // Not loaded yet, trigger loading
28
+ }
29
+
30
+ return infiniteItems.value[index] !== undefined
31
+ }
32
+
33
+ // Helper to get item or loading indicator
34
+ const getItem = (index: number) => {
35
+ // If this is the loading indicator row
36
+ if (hasMoreData.value && index === infiniteItems.value.length) {
37
+ return isLoading.value ? '⏳ Loading more...' : '↓ Scroll for more'
38
+ }
39
+
40
+ return infiniteItems.value[index]
41
+ }
42
+
43
+ const loadMoreRows = async (startIndex: number, stopIndex: number) => {
44
+ // Block all new requests if one is already in progress
45
+ if (isLoading.value) {
46
+ console.log(`[loadMoreRows] Already loading, rejecting request for ${startIndex}-${stopIndex}`)
47
+
48
+ return
49
+ }
50
+
51
+ isLoading.value = true
52
+ const currentLength = infiniteItems.value.length
53
+
54
+ console.log(`[loadMoreRows] Loading batch of 50 items, cursor: ${apiCursor.value}, current items: ${currentLength}`)
55
+
56
+ try {
57
+ // Simulate API call with cursor
58
+ // In real implementation: const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
59
+ await new Promise((resolve) => setTimeout(resolve, 1000))
60
+
61
+ // In a real implementation, you'd use the cursor and response data:
62
+ // const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
63
+ // const { items, nextCursor, hasMore } = await response.json();
64
+ // apiCursor.value = nextCursor;
65
+ // hasMoreData.value = hasMore;
66
+ //
67
+ // Then append the items from the API:
68
+ // const newItems = [...infiniteItems.value, ...items];
69
+ // infiniteItems.value = newItems;
70
+
71
+ // Simulate API response: generate 50 items
72
+ const batchSize = 50
73
+ const itemsToAdd = []
74
+
75
+ for (let i = 0; i < batchSize; i++) {
76
+ const itemIndex = currentLength + i
77
+
78
+ itemsToAdd.push(`Item ${itemIndex + 1}`)
79
+ if (itemIndex > highestLoadedIndex.value) {
80
+ highestLoadedIndex.value = itemIndex
81
+ }
82
+ }
83
+
84
+ // Simulate updating cursor based on response
85
+ apiCursor.value = `cursor_after_${currentLength + batchSize - 1}`
86
+
87
+ // Append all new items at once
88
+ infiniteItems.value = [...infiniteItems.value, ...itemsToAdd]
89
+
90
+ console.log(`[loadMoreRows] Loaded ${batchSize} items, total: ${infiniteItems.value.length}, cursor: ${apiCursor.value}`)
91
+
92
+ // Check if we've reached the limit (simulate end of data)
93
+ if (infiniteItems.value.length >= 500) {
94
+ hasMoreData.value = false
95
+ console.log('[loadMoreRows] Reached limit, no more data to load')
96
+ }
97
+ } catch (error) {
98
+ console.error('[loadMoreRows] Error loading items:', error)
99
+ } finally {
100
+ isLoading.value = false
101
+ }
102
+ }
103
+
104
+ const infiniteRowCount = computed(() => {
105
+ const itemsLength = infiniteItems.value.length
106
+
107
+ if (!hasMoreData.value) {
108
+ // No more data, show only loaded items
109
+ return itemsLength
110
+ }
111
+
112
+ // Show loaded items + 1 loading indicator row
113
+ return itemsLength + 1
114
+ })
115
+
116
+ // Load first batch of items on mount or when switching to infinite tab
117
+ const loadInitialItems = async () => {
118
+ if (infiniteItems.value.length === 0) {
119
+ await loadMoreRows(0, 49) // Indices are ignored, always loads 50 items
120
+ }
121
+ }
122
+
123
+ onMounted(() => {
124
+ loadInitialItems()
125
+ })
126
+ </script>
127
+
128
+ <template>
129
+ <p class="info">
130
+ Loaded {{ loadedCount }} of {{ infiniteRowCount }} items
131
+ <span v-if="isLoading" class="loading"> (Loading...)</span>
132
+ </p>
133
+ <x-infinite-loader
134
+ :is-row-loaded="isRowLoaded"
135
+ :load-more-rows="loadMoreRows"
136
+ :row-count="infiniteRowCount"
137
+ :minimum-batch-size="50"
138
+ :threshold="10"
139
+ >
140
+ <template #default="{ onRowsRendered }">
141
+ <x-virtual-list
142
+ :row-count="infiniteRowCount"
143
+ :row-height="50"
144
+ class="h-96 border border-gray-200 rounded-md bg-white"
145
+ :on-rows-rendered="onRowsRendered"
146
+ >
147
+ <template #row="{ index, style }">
148
+ <div
149
+ :style="style"
150
+ :class="[
151
+ 'h-12 flex items-center justify-center border-b border-gray-200',
152
+ { 'loading-indicator': index === infiniteItems.length && hasMoreData }
153
+ ]"
154
+ >
155
+ {{ getItem(index) }}
156
+ </div>
157
+ </template>
158
+ </x-virtual-list>
159
+ </template>
160
+ </x-infinite-loader>
161
+ </template>
@@ -78,9 +78,22 @@ const itemsSorted = computed<Book[]>(() => {
78
78
  <template #item-published="{ item }">
79
79
  {{ formatDate(item.published) }}
80
80
  </template>
81
+
81
82
  <template #item-status="{ item }">
82
83
  <x-tag size="xs" color="primary" rounded>{{ item.status }}</x-tag>
83
84
  </template>
85
+
86
+ <template #footer>
87
+ <tfoot>
88
+ <tr>
89
+ <td colspan="6" class="text-center py-2">
90
+ <p class="text-sm text-gray-500 font-medium">
91
+ Footer content
92
+ </p>
93
+ </td>
94
+ </tr>
95
+ </tfoot>
96
+ </template>
84
97
  </x-table>
85
98
  </x-card>
86
99
 
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { XVirtualGrid } from '@indielayer/ui'
3
+ import UsageDemoCode from './usage.vue?raw'
4
+ import UsageDemo from './usage.vue'
5
+
6
+ const title = 'VirtualGrid'
7
+ const description = 'VirtualGrid is a component that allows you to render a grid of items in a virtualized way. It is a high-performance component that is used to render large grids of items.'
8
+ const components = [XVirtualGrid]
9
+ const demos = [{
10
+ name: 'Usage',
11
+ description: '',
12
+ code: UsageDemoCode,
13
+ component: UsageDemo,
14
+ }]
15
+ const back = 'virtualList'
16
+ const next = ''
17
+ </script>
18
+
19
+ <template>
20
+ <document-page
21
+ github="https://github.com/indielayer/ui/blob/main/packages/ui/docs/pages/component/virtualGrid"
22
+ :title="title"
23
+ :description="description"
24
+ :components="components"
25
+ :demos="demos"
26
+ :back="back"
27
+ :next="next"
28
+ />
29
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ const gridRowCount = 1000
3
+ const gridColumnCount = 1000
4
+ </script>
5
+
6
+ <template>
7
+ <x-virtual-grid
8
+ :row-count="gridRowCount"
9
+ :column-count="gridColumnCount"
10
+ :row-height="48"
11
+ :column-width="100"
12
+ class="h-96 border border-gray-200 rounded-md bg-white w-full"
13
+ >
14
+ <template #cell="{ rowIndex, columnIndex, style }">
15
+ <div :style="style" class="h-12 flex items-center justify-center border border-gray-200">
16
+ R{{ rowIndex }}, C{{ columnIndex }}
17
+ </div>
18
+ </template>
19
+ </x-virtual-grid>
20
+ </template>
@@ -0,0 +1,75 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { XVirtualList, useDynamicRowHeight } from '@indielayer/ui'
4
+
5
+ const loremTexts = [
6
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
7
+ 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
8
+ 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
9
+ 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.',
10
+ 'Totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.',
11
+ 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit.',
12
+ 'Sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.',
13
+ 'Ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.',
14
+ 'Lorem ipsum dolor sit amet.',
15
+ 'Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.',
16
+ ]
17
+
18
+ const items = Array.from({ length: 1000 }, (_, i) => ({
19
+ id: i,
20
+ text: loremTexts[i % loremTexts.length],
21
+ }))
22
+
23
+ const collapsedRows = ref<Set<number>>(new Set())
24
+
25
+ const toggleRow = (index: number) => {
26
+ const newSet = new Set(collapsedRows.value)
27
+
28
+ if (newSet.has(index)) {
29
+ newSet.delete(index)
30
+ } else {
31
+ newSet.add(index)
32
+ }
33
+ collapsedRows.value = newSet
34
+ }
35
+
36
+ const isRowCollapsed = (index: number) => {
37
+ return collapsedRows.value.has(index)
38
+ }
39
+
40
+ const getText = (index: number) => {
41
+ return items[index].text
42
+ }
43
+
44
+ const rowHeight = useDynamicRowHeight({
45
+ defaultRowHeight: 50,
46
+ })
47
+ </script>
48
+
49
+ <template>
50
+ <x-virtual-list
51
+ :row-count="items.length"
52
+ :row-height="rowHeight"
53
+ style="height: 500px; border: 1px solid #ccc;"
54
+ >
55
+ <template #row="{ index, style }">
56
+ <div
57
+ :style="style"
58
+ :class="[
59
+ 'flex items-start py-3 px-4 border-b border-gray-200 cursor-pointer transition-colors gap-2',
60
+ index % 2 === 0 ? 'bg-white' : 'bg-gray-50',
61
+ 'hover:bg-gray-200',
62
+ isRowCollapsed(index) ? 'whitespace-nowrap overflow-hidden text-ellipsis' : ''
63
+ ]"
64
+ @click="toggleRow(index)"
65
+ >
66
+ <span class="flex-shrink-0 w-6 h-6 inline-flex items-center justify-center text-sm">
67
+ {{ isRowCollapsed(index) ? '➕' : '➖' }}
68
+ </span>
69
+ <span class="flex-1 leading-normal">
70
+ <strong class="text-gray-700 mr-2">{{ index }}:</strong>{{ getText(index) }}
71
+ </span>
72
+ </div>
73
+ </template>
74
+ </x-virtual-list>
75
+ </template>
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ import { XVirtualList } from '@indielayer/ui'
3
+ import UsageDemoCode from './usage.vue?raw'
4
+ import UsageDemo from './usage.vue'
5
+ import DynamicHeightDemoCode from './dynamicHeight.vue?raw'
6
+ import DynamicHeightDemo from './dynamicHeight.vue'
7
+
8
+ const title = 'VirtualList'
9
+ const description = 'VirtualList is a component that allows you to render a list of items in a virtualized way. It is a high-performance component that is used to render large lists of items.'
10
+ const components = [XVirtualList]
11
+ const demos = [{
12
+ name: 'Usage',
13
+ description: '',
14
+ code: UsageDemoCode,
15
+ component: UsageDemo,
16
+ }, {
17
+ name: 'Dynamic Height',
18
+ description: '',
19
+ code: DynamicHeightDemoCode,
20
+ component: DynamicHeightDemo,
21
+ }]
22
+ const back = ''
23
+ const next = ''
24
+ </script>
25
+
26
+ <template>
27
+ <document-page
28
+ github="https://github.com/indielayer/ui/blob/main/packages/ui/docs/pages/component/virtualList"
29
+ :title="title"
30
+ :description="description"
31
+ :components="components"
32
+ :demos="demos"
33
+ :back="back"
34
+ :next="next"
35
+ />
36
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ const listItems = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`)
3
+ </script>
4
+
5
+ <template>
6
+ <x-virtual-list
7
+ :row-count="listItems.length"
8
+ :row-height="48"
9
+ class="h-96 border border-gray-200 rounded-md bg-white"
10
+ >
11
+ <template #row="{ index, style }">
12
+ <div :style="style" class="h-12 flex items-center justify-center border-b border-gray-200">
13
+ {{ listItems[index] }}
14
+ </div>
15
+ </template>
16
+ </x-virtual-list>
17
+ </template>
@@ -1 +1 @@
1
- [{"name":"Accordion","description":"Accordion is a component that allows you to collapse and expand content.","url":"/component/accordion"},{"name":"Alert","description":"Alerts are used to communicate a state that affects a system, feature or page.","url":"/component/alert"},{"name":"Avatar","description":"Avatars are used to represent a user.","url":"/component/avatar"},{"name":"Badge","description":"Badges are used to display a small amount of information.","url":"/component/badge"},{"name":"Breadcrumbs","description":"Breadcrumbs are used to indicate the current page\\","url":"/component/breadcrumbs"},{"name":"Button","description":"Buttons allow users to perform actions and make choices with a single click. They are commonly used for submitting forms, opening dialogs or menus, and executing commands. Buttons can be customized with different styles, sizes, and icons to fit various use cases.","url":"/component/button"},{"name":"Card","description":"Cards are used to display content in an organized manner.","url":"/component/card"},{"name":"Carousel","description":"A carousel is a rotating set of images.","url":"/component/carousel"},{"name":"Checkbox","description":"Checkboxes enable users to select one or multiple options from a list. They are ideal for toggling settings or making selections where more than one choice is allowed. Each checkbox can be checked or unchecked independently.","url":"/component/checkbox"},{"name":"Container","description":"Containers provide a flexible layout element for grouping and organizing content. They help structure pages by controlling width, alignment, and spacing, ensuring consistent presentation across different sections.","url":"/component/container"},{"name":"Datepicker","description":"The datepicker component is used to select a date or time.","url":"/component/datepicker"},{"name":"Divider","description":"Dividers are used to separate content.","url":"/component/divider"},{"name":"Drawer","description":"Drawers slide in from the edge of the screen to present navigation, options, or additional content without disrupting the main view. They are ideal for menus, filters, or contextual panels.","url":"/component/drawer"},{"name":"Form","description":"Forms provide a structured way to collect and validate user input, such as text, selections, and files. They support features like validation, grouping, and submission handling, making it easy to build interactive and accessible data entry interfaces.","url":"/component/form"},{"name":"FormGroup","description":"Form groups organize related form elements together, providing structure and shared validation. They help group checkboxes, radio buttons, or inputs that belong to the same logical section.","url":"/component/formGroup"},{"name":"Icon","description":"Icons visually represent actions, objects, or concepts, enhancing usability and recognition in interfaces. They can be used alone or alongside text for buttons, menus, and indicators.","url":"/component/icon"},{"name":"Image","description":"Image is used to load an image file with a skeleton as placeholder and on load display the image.","url":"/component/image"},{"name":"Input","description":"This is a text input component that allows users to enter and edit text.","url":"/component/input"},{"name":"Link","description":"Links are used to navigate to a different page.","url":"/component/link"},{"name":"Loader","description":"Loader component is used to show a loading state.","url":"/component/loader"},{"name":"Menu","description":"Menus are used to display a list of options.","url":"/component/menu"},{"name":"Modal","description":"Modals are used to display content on top of the current page.","url":"/component/modal"},{"name":"Notifications","description":"Notifications provide timely feedback or alerts to users, such as success messages, warnings, or errors. They help keep users informed about important events or actions.","url":"/component/notifications"},{"name":"Pagination","description":"Pagination divides large sets of content into manageable pages, allowing users to navigate through items efficiently. It is commonly used in tables, lists, and search results to improve usability and performance.","url":"/component/pagination"},{"name":"Popover","description":"Popovers display additional information or interactive content in a floating overlay, anchored to a trigger element. They are useful for tooltips, menus, or contextual actions without navigating away from the current view.","url":"/component/popover"},{"name":"Progress","description":"Progress indicators visually communicate the completion status of a task or process, such as file uploads or data loading. They help users understand how much work remains.","url":"/component/progress"},{"name":"QR Code","description":"The QR Code component is used to generate a QR code.","url":"/component/qrCode"},{"name":"Radio","description":"Radios allow the user to select one option from a set. Use radio buttons for exclusive selection if you think that the user needs to see all available options side-by-side.","url":"/component/radio"},{"name":"Scroll","description":"The Scroll component enhances scrolling experiences by adding visual cues, such as inner shadows, to indicate additional content. It is useful for horizontally or vertically scrolling lists and containers.","url":"/component/scroll"},{"name":"Select","description":"Selects allow the user to select one or more options from a set.","url":"/component/select"},{"name":"Skeleton","description":"Skeletons provide placeholder elements that mimic the layout of content while data is loading. They improve perceived performance by giving users a visual cue that content is being fetched.","url":"/component/skeleton"},{"name":"Slider","description":"Sliders allow users to select a numeric value or range by dragging a handle along a track. They are ideal for adjusting settings such as volume, brightness, or price filters.","url":"/component/slider"},{"name":"Spacer","description":"Spacers create adjustable gaps between elements within flex layouts, helping to control spacing and alignment without using margin or padding directly.","url":"/component/spacer"},{"name":"Spinner","description":"Spinners visually indicate that a background process or operation is ongoing, such as loading data or submitting a form. They help manage user expectations by signaling that the system is working.","url":"/component/spinner"},{"name":"Stepper","description":"Stepper is a navigation component that guides users through the steps of a task.","url":"/component/stepper"},{"name":"Table","description":"Tables are used to display data in a tabular format.","url":"/component/table"},{"name":"Tabs","description":"Tabs are used to navigate through a set of views.","url":"/component/tabs"},{"name":"Tag","description":"Tags are compact elements used to display brief information, such as categories, statuses, or labels. They help organize content and provide quick context or filtering options within lists and interfaces.","url":"/component/tag"},{"name":"Textarea","description":"Textareas provide a multi-line input field for users to enter longer text, such as comments, descriptions, or messages. They support resizing and can be customized for different use cases.","url":"/component/textarea"},{"name":"Toggle","description":"Toggles provide a simple switch interface for enabling or disabling a setting, or switching between two mutually exclusive states. They are ideal for binary options such as on/off, active/inactive, or show/hide.","url":"/component/toggle"},{"name":"Tooltip","description":"Tooltips are used to display a message when hovering over an element.","url":"/component/tooltip"},{"name":"Upload","description":"Upload is a component that allows you to upload files.","url":"/component/upload"}]
1
+ [{"name":"Accordion","description":"Accordion is a component that allows you to collapse and expand content.","url":"/component/accordion"},{"name":"Alert","description":"Alerts are used to communicate a state that affects a system, feature or page.","url":"/component/alert"},{"name":"Avatar","description":"Avatars are used to represent a user.","url":"/component/avatar"},{"name":"Badge","description":"Badges are used to display a small amount of information.","url":"/component/badge"},{"name":"Breadcrumbs","description":"Breadcrumbs are used to indicate the current page\\","url":"/component/breadcrumbs"},{"name":"Button","description":"Buttons allow users to perform actions and make choices with a single click. They are commonly used for submitting forms, opening dialogs or menus, and executing commands. Buttons can be customized with different styles, sizes, and icons to fit various use cases.","url":"/component/button"},{"name":"Card","description":"Cards are used to display content in an organized manner.","url":"/component/card"},{"name":"Carousel","description":"A carousel is a rotating set of images.","url":"/component/carousel"},{"name":"Checkbox","description":"Checkboxes enable users to select one or multiple options from a list. They are ideal for toggling settings or making selections where more than one choice is allowed. Each checkbox can be checked or unchecked independently.","url":"/component/checkbox"},{"name":"Container","description":"Containers provide a flexible layout element for grouping and organizing content. They help structure pages by controlling width, alignment, and spacing, ensuring consistent presentation across different sections.","url":"/component/container"},{"name":"Datepicker","description":"The datepicker component is used to select a date or time.","url":"/component/datepicker"},{"name":"Divider","description":"Dividers are used to separate content.","url":"/component/divider"},{"name":"Drawer","description":"Drawers slide in from the edge of the screen to present navigation, options, or additional content without disrupting the main view. They are ideal for menus, filters, or contextual panels.","url":"/component/drawer"},{"name":"Form","description":"Forms provide a structured way to collect and validate user input, such as text, selections, and files. They support features like validation, grouping, and submission handling, making it easy to build interactive and accessible data entry interfaces.","url":"/component/form"},{"name":"FormGroup","description":"Form groups organize related form elements together, providing structure and shared validation. They help group checkboxes, radio buttons, or inputs that belong to the same logical section.","url":"/component/formGroup"},{"name":"Icon","description":"Icons visually represent actions, objects, or concepts, enhancing usability and recognition in interfaces. They can be used alone or alongside text for buttons, menus, and indicators.","url":"/component/icon"},{"name":"Image","description":"Image is used to load an image file with a skeleton as placeholder and on load display the image.","url":"/component/image"},{"name":"InfiniteLoader","description":"InfiniteLoader is a component that allows you to load data on demand as users scroll through a list.","url":"/component/infiniteLoader"},{"name":"Input","description":"This is a text input component that allows users to enter and edit text.","url":"/component/input"},{"name":"Link","description":"Links are used to navigate to a different page.","url":"/component/link"},{"name":"Loader","description":"Loader component is used to show a loading state.","url":"/component/loader"},{"name":"Menu","description":"Menus are used to display a list of options.","url":"/component/menu"},{"name":"Modal","description":"Modals are used to display content on top of the current page.","url":"/component/modal"},{"name":"Notifications","description":"Notifications provide timely feedback or alerts to users, such as success messages, warnings, or errors. They help keep users informed about important events or actions.","url":"/component/notifications"},{"name":"Pagination","description":"Pagination divides large sets of content into manageable pages, allowing users to navigate through items efficiently. It is commonly used in tables, lists, and search results to improve usability and performance.","url":"/component/pagination"},{"name":"Popover","description":"Popovers display additional information or interactive content in a floating overlay, anchored to a trigger element. They are useful for tooltips, menus, or contextual actions without navigating away from the current view.","url":"/component/popover"},{"name":"Progress","description":"Progress indicators visually communicate the completion status of a task or process, such as file uploads or data loading. They help users understand how much work remains.","url":"/component/progress"},{"name":"QR Code","description":"The QR Code component is used to generate a QR code.","url":"/component/qrCode"},{"name":"Radio","description":"Radios allow the user to select one option from a set. Use radio buttons for exclusive selection if you think that the user needs to see all available options side-by-side.","url":"/component/radio"},{"name":"Scroll","description":"The Scroll component enhances scrolling experiences by adding visual cues, such as inner shadows, to indicate additional content. It is useful for horizontally or vertically scrolling lists and containers.","url":"/component/scroll"},{"name":"Select","description":"Selects allow the user to select one or more options from a set.","url":"/component/select"},{"name":"Skeleton","description":"Skeletons provide placeholder elements that mimic the layout of content while data is loading. They improve perceived performance by giving users a visual cue that content is being fetched.","url":"/component/skeleton"},{"name":"Slider","description":"Sliders allow users to select a numeric value or range by dragging a handle along a track. They are ideal for adjusting settings such as volume, brightness, or price filters.","url":"/component/slider"},{"name":"Spacer","description":"Spacers create adjustable gaps between elements within flex layouts, helping to control spacing and alignment without using margin or padding directly.","url":"/component/spacer"},{"name":"Spinner","description":"Spinners visually indicate that a background process or operation is ongoing, such as loading data or submitting a form. They help manage user expectations by signaling that the system is working.","url":"/component/spinner"},{"name":"Stepper","description":"Stepper is a navigation component that guides users through the steps of a task.","url":"/component/stepper"},{"name":"Table","description":"Tables are used to display data in a tabular format.","url":"/component/table"},{"name":"Tabs","description":"Tabs are used to navigate through a set of views.","url":"/component/tabs"},{"name":"Tag","description":"Tags are compact elements used to display brief information, such as categories, statuses, or labels. They help organize content and provide quick context or filtering options within lists and interfaces.","url":"/component/tag"},{"name":"Textarea","description":"Textareas provide a multi-line input field for users to enter longer text, such as comments, descriptions, or messages. They support resizing and can be customized for different use cases.","url":"/component/textarea"},{"name":"Toggle","description":"Toggles provide a simple switch interface for enabling or disabling a setting, or switching between two mutually exclusive states. They are ideal for binary options such as on/off, active/inactive, or show/hide.","url":"/component/toggle"},{"name":"Tooltip","description":"Tooltips are used to display a message when hovering over an element.","url":"/component/tooltip"},{"name":"Upload","description":"Upload is a component that allows you to upload files.","url":"/component/upload"},{"name":"VirtualGrid","description":"VirtualGrid is a component that allows you to render a grid of items in a virtualized way. It is a high-performance component that is used to render large grids of items.","url":"/component/virtualGrid"},{"name":"VirtualList","description":"VirtualList is a component that allows you to render a list of items in a virtualized way. It is a high-performance component that is used to render large lists of items.","url":"/component/virtualList"}]