@indielayer/ui 1.16.0 → 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.
- package/docs/components/menu/DocsMenu.vue +3 -0
- package/docs/pages/component/infiniteLoader/composable.vue +168 -0
- package/docs/pages/component/infiniteLoader/index.vue +36 -0
- package/docs/pages/component/infiniteLoader/usage.vue +161 -0
- package/docs/pages/component/virtualGrid/index.vue +29 -0
- package/docs/pages/component/virtualGrid/usage.vue +20 -0
- package/docs/pages/component/virtualList/dynamicHeight.vue +75 -0
- package/docs/pages/component/virtualList/index.vue +36 -0
- package/docs/pages/component/virtualList/usage.vue +17 -0
- package/docs/search/components.json +1 -1
- package/lib/components/select/Select.vue.js +35 -35
- package/lib/components/table/Table.vue.js +1 -1
- package/lib/composables/useVirtualList.d.ts +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +88 -76
- package/lib/index.umd.js +4 -4
- package/lib/install.js +15 -7
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.test.d.ts +1 -0
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.d.ts +49 -0
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.js +21 -0
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue2.js +4 -0
- package/lib/virtual/components/virtualGrid/VirtualGrid.vue.d.ts +185 -0
- package/lib/virtual/components/virtualGrid/VirtualGrid.vue.js +241 -0
- package/lib/virtual/components/virtualGrid/VirtualGrid.vue2.js +4 -0
- package/lib/virtual/components/virtualGrid/types.d.ts +138 -0
- package/lib/virtual/components/virtualList/VirtualList.test.d.ts +1 -0
- package/lib/virtual/components/virtualList/VirtualList.vue.d.ts +135 -0
- package/lib/virtual/components/virtualList/VirtualList.vue.js +157 -0
- package/lib/virtual/components/virtualList/VirtualList.vue2.js +4 -0
- package/lib/virtual/components/virtualList/isDynamicRowHeight.d.ts +2 -0
- package/lib/virtual/components/virtualList/isDynamicRowHeight.js +6 -0
- package/lib/virtual/components/virtualList/types.d.ts +115 -0
- package/lib/virtual/components/virtualList/useDynamicRowHeight.d.ts +7 -0
- package/lib/virtual/components/virtualList/useDynamicRowHeight.js +69 -0
- package/lib/virtual/components/virtualList/useDynamicRowHeight.test.d.ts +1 -0
- package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.d.ts +8 -0
- package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.js +41 -0
- package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.test.d.ts +1 -0
- package/lib/virtual/composables/infinite-loader/types.d.ts +30 -0
- package/lib/virtual/composables/infinite-loader/useInfiniteLoader.d.ts +6 -0
- package/lib/virtual/composables/infinite-loader/useInfiniteLoader.js +42 -0
- package/lib/virtual/composables/infinite-loader/useInfiniteLoader.test.d.ts +1 -0
- package/lib/virtual/core/createCachedBounds.d.ts +6 -0
- package/lib/virtual/core/createCachedBounds.js +55 -0
- package/lib/virtual/core/getEstimatedSize.d.ts +6 -0
- package/lib/virtual/core/getEstimatedSize.js +22 -0
- package/lib/virtual/core/getOffsetForIndex.d.ts +11 -0
- package/lib/virtual/core/getOffsetForIndex.js +40 -0
- package/lib/virtual/core/getStartStopIndices.d.ts +13 -0
- package/lib/virtual/core/getStartStopIndices.js +31 -0
- package/lib/virtual/core/getStartStopIndices.test.d.ts +1 -0
- package/lib/virtual/core/types.d.ts +11 -0
- package/lib/virtual/core/useCachedBounds.d.ts +7 -0
- package/lib/virtual/core/useCachedBounds.js +18 -0
- package/lib/virtual/core/useIsRtl.d.ts +2 -0
- package/lib/virtual/core/useIsRtl.js +15 -0
- package/lib/virtual/core/useItemSize.d.ts +5 -0
- package/lib/virtual/core/useItemSize.js +27 -0
- package/lib/virtual/core/useVirtualizer.d.ts +33 -0
- package/lib/virtual/core/useVirtualizer.js +171 -0
- package/lib/virtual/index.d.ts +9 -0
- package/lib/virtual/test-utils/mockResizeObserver.d.ts +15 -0
- package/lib/virtual/types.d.ts +2 -0
- package/lib/virtual/utils/adjustScrollOffsetForRtl.d.ts +7 -0
- package/lib/virtual/utils/adjustScrollOffsetForRtl.js +24 -0
- package/lib/virtual/utils/areArraysEqual.d.ts +1 -0
- package/lib/virtual/utils/assert.d.ts +1 -0
- package/lib/virtual/utils/assert.js +7 -0
- package/lib/virtual/utils/getRTLOffsetType.d.ts +2 -0
- package/lib/virtual/utils/getRTLOffsetType.js +13 -0
- package/lib/virtual/utils/getScrollbarSize.d.ts +2 -0
- package/lib/virtual/utils/getScrollbarSize.js +11 -0
- package/lib/virtual/utils/isRtl.d.ts +1 -0
- package/lib/virtual/utils/isRtl.js +12 -0
- package/lib/virtual/utils/parseNumericStyleValue.d.ts +2 -0
- package/lib/virtual/utils/parseNumericStyleValue.js +15 -0
- package/lib/virtual/utils/shallowCompare.d.ts +1 -0
- package/lib/virtual/utils/shallowCompare.js +14 -0
- package/package.json +1 -1
- package/src/components/select/Select.vue +3 -2
- package/src/components/table/Table.vue +1 -1
- package/src/composables/useVirtualList.ts +1 -1
- package/src/index.ts +1 -0
- package/src/install.ts +9 -3
- package/src/version.ts +1 -1
- package/src/virtual/README.md +285 -0
- package/src/virtual/components/infiniteLoader/InfiniteLoader.test.ts +96 -0
- package/src/virtual/components/infiniteLoader/InfiniteLoader.vue +18 -0
- package/src/virtual/components/virtualGrid/VirtualGrid.vue +322 -0
- package/src/virtual/components/virtualGrid/types.ts +160 -0
- package/src/virtual/components/virtualList/VirtualList.test.ts +47 -0
- package/src/virtual/components/virtualList/VirtualList.vue +233 -0
- package/src/virtual/components/virtualList/isDynamicRowHeight.ts +13 -0
- package/src/virtual/components/virtualList/types.ts +127 -0
- package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +183 -0
- package/src/virtual/components/virtualList/useDynamicRowHeight.ts +147 -0
- package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.test.ts +141 -0
- package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.ts +82 -0
- package/src/virtual/composables/infinite-loader/types.ts +36 -0
- package/src/virtual/composables/infinite-loader/useInfiniteLoader.test.ts +236 -0
- package/src/virtual/composables/infinite-loader/useInfiniteLoader.ts +88 -0
- package/src/virtual/core/createCachedBounds.ts +72 -0
- package/src/virtual/core/getEstimatedSize.ts +29 -0
- package/src/virtual/core/getOffsetForIndex.ts +90 -0
- package/src/virtual/core/getStartStopIndices.test.ts +45 -0
- package/src/virtual/core/getStartStopIndices.ts +71 -0
- package/src/virtual/core/types.ts +17 -0
- package/src/virtual/core/useCachedBounds.ts +21 -0
- package/src/virtual/core/useIsRtl.ts +25 -0
- package/src/virtual/core/useItemSize.ts +34 -0
- package/src/virtual/core/useVirtualizer.ts +294 -0
- package/src/virtual/index.ts +25 -0
- package/src/virtual/test-utils/mockResizeObserver.ts +162 -0
- package/src/virtual/types.ts +3 -0
- package/src/virtual/utils/adjustScrollOffsetForRtl.ts +37 -0
- package/src/virtual/utils/areArraysEqual.ts +13 -0
- package/src/virtual/utils/assert.ts +10 -0
- package/src/virtual/utils/getRTLOffsetType.ts +51 -0
- package/src/virtual/utils/getScrollbarSize.ts +24 -0
- package/src/virtual/utils/isRtl.ts +13 -0
- package/src/virtual/utils/parseNumericStyleValue.ts +19 -0
- package/src/virtual/utils/shallowCompare.ts +29 -0
- 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>
|
|
@@ -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"}]
|