@indielayer/ui 1.16.0 → 1.18.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 (156) hide show
  1. package/README.md +2 -2
  2. package/docs/assets/css/tailwind.css +6 -0
  3. package/docs/components/common/CodePreview.vue +14 -9
  4. package/docs/components/common/DocsFeatures.vue +41 -0
  5. package/docs/components/common/DocsHero.vue +216 -0
  6. package/docs/components/common/DocumentPage.vue +99 -112
  7. package/docs/components/common/ExampleBlocks.vue +157 -0
  8. package/docs/components/menu/DocsMenu.vue +3 -0
  9. package/docs/components/toolbar/Toolbar.vue +11 -2
  10. package/docs/components/toolbar/ToolbarColorToggle.vue +4 -4
  11. package/docs/components/toolbar/ToolbarSearch.vue +59 -62
  12. package/docs/composables/useDocMeta.ts +47 -0
  13. package/docs/icons.ts +28 -0
  14. package/docs/layouts/default.vue +1 -3
  15. package/docs/layouts/simple.vue +3 -1
  16. package/docs/main.ts +5 -0
  17. package/docs/pages/colors.vue +56 -47
  18. package/docs/pages/component/infiniteLoader/composable.vue +168 -0
  19. package/docs/pages/component/infiniteLoader/index.vue +36 -0
  20. package/docs/pages/component/infiniteLoader/usage.vue +161 -0
  21. package/docs/pages/component/select/size.vue +1 -1
  22. package/docs/pages/component/select/usage.vue +14 -7
  23. package/docs/pages/component/virtualGrid/index.vue +29 -0
  24. package/docs/pages/component/virtualGrid/usage.vue +20 -0
  25. package/docs/pages/component/virtualList/dynamicHeight.vue +75 -0
  26. package/docs/pages/component/virtualList/index.vue +36 -0
  27. package/docs/pages/component/virtualList/usage.vue +17 -0
  28. package/docs/pages/error.vue +5 -3
  29. package/docs/pages/icons.vue +64 -54
  30. package/docs/pages/index.vue +93 -82
  31. package/docs/pages/typography.vue +38 -28
  32. package/docs/router/index.ts +31 -3
  33. package/docs/search/components.json +1 -1
  34. package/docs/search/index.json +1 -0
  35. package/lib/components/container/theme/Container.base.theme.js +1 -1
  36. package/lib/components/divider/theme/Divider.base.theme.js +1 -1
  37. package/lib/components/input/Input.vue.js +23 -24
  38. package/lib/components/select/Select.vue.d.ts +16 -27
  39. package/lib/components/select/Select.vue.js +452 -345
  40. package/lib/components/table/Table.vue.js +1 -1
  41. package/lib/composables/useVirtualList.d.ts +1 -1
  42. package/lib/index.d.ts +1 -0
  43. package/lib/index.js +88 -76
  44. package/lib/index.umd.js +4 -4
  45. package/lib/install.js +15 -7
  46. package/lib/version.d.ts +1 -1
  47. package/lib/version.js +1 -1
  48. package/lib/virtual/components/infiniteLoader/InfiniteLoader.test.d.ts +1 -0
  49. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.d.ts +49 -0
  50. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.js +21 -0
  51. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue2.js +4 -0
  52. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.d.ts +185 -0
  53. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.js +241 -0
  54. package/lib/virtual/components/virtualGrid/VirtualGrid.vue2.js +4 -0
  55. package/lib/virtual/components/virtualGrid/types.d.ts +138 -0
  56. package/lib/virtual/components/virtualList/VirtualList.test.d.ts +1 -0
  57. package/lib/virtual/components/virtualList/VirtualList.vue.d.ts +135 -0
  58. package/lib/virtual/components/virtualList/VirtualList.vue.js +159 -0
  59. package/lib/virtual/components/virtualList/VirtualList.vue2.js +4 -0
  60. package/lib/virtual/components/virtualList/isDynamicRowHeight.d.ts +2 -0
  61. package/lib/virtual/components/virtualList/isDynamicRowHeight.js +6 -0
  62. package/lib/virtual/components/virtualList/types.d.ts +115 -0
  63. package/lib/virtual/components/virtualList/useDynamicRowHeight.d.ts +7 -0
  64. package/lib/virtual/components/virtualList/useDynamicRowHeight.js +68 -0
  65. package/lib/virtual/components/virtualList/useDynamicRowHeight.test.d.ts +1 -0
  66. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.d.ts +8 -0
  67. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.js +41 -0
  68. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.test.d.ts +1 -0
  69. package/lib/virtual/composables/infinite-loader/types.d.ts +30 -0
  70. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.d.ts +6 -0
  71. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.js +42 -0
  72. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.test.d.ts +1 -0
  73. package/lib/virtual/core/createCachedBounds.d.ts +6 -0
  74. package/lib/virtual/core/createCachedBounds.js +55 -0
  75. package/lib/virtual/core/getEstimatedSize.d.ts +6 -0
  76. package/lib/virtual/core/getEstimatedSize.js +22 -0
  77. package/lib/virtual/core/getOffsetForIndex.d.ts +11 -0
  78. package/lib/virtual/core/getOffsetForIndex.js +40 -0
  79. package/lib/virtual/core/getStartStopIndices.d.ts +13 -0
  80. package/lib/virtual/core/getStartStopIndices.js +31 -0
  81. package/lib/virtual/core/getStartStopIndices.test.d.ts +1 -0
  82. package/lib/virtual/core/types.d.ts +11 -0
  83. package/lib/virtual/core/useCachedBounds.d.ts +7 -0
  84. package/lib/virtual/core/useCachedBounds.js +18 -0
  85. package/lib/virtual/core/useIsRtl.d.ts +2 -0
  86. package/lib/virtual/core/useIsRtl.js +15 -0
  87. package/lib/virtual/core/useItemSize.d.ts +5 -0
  88. package/lib/virtual/core/useItemSize.js +27 -0
  89. package/lib/virtual/core/useVirtualizer.d.ts +33 -0
  90. package/lib/virtual/core/useVirtualizer.js +171 -0
  91. package/lib/virtual/index.d.ts +9 -0
  92. package/lib/virtual/test-utils/mockResizeObserver.d.ts +15 -0
  93. package/lib/virtual/types.d.ts +2 -0
  94. package/lib/virtual/utils/adjustScrollOffsetForRtl.d.ts +7 -0
  95. package/lib/virtual/utils/adjustScrollOffsetForRtl.js +24 -0
  96. package/lib/virtual/utils/areArraysEqual.d.ts +1 -0
  97. package/lib/virtual/utils/assert.d.ts +1 -0
  98. package/lib/virtual/utils/assert.js +7 -0
  99. package/lib/virtual/utils/getRTLOffsetType.d.ts +2 -0
  100. package/lib/virtual/utils/getRTLOffsetType.js +13 -0
  101. package/lib/virtual/utils/getScrollbarSize.d.ts +2 -0
  102. package/lib/virtual/utils/getScrollbarSize.js +11 -0
  103. package/lib/virtual/utils/isRtl.d.ts +1 -0
  104. package/lib/virtual/utils/isRtl.js +12 -0
  105. package/lib/virtual/utils/parseNumericStyleValue.d.ts +2 -0
  106. package/lib/virtual/utils/parseNumericStyleValue.js +15 -0
  107. package/lib/virtual/utils/shallowCompare.d.ts +1 -0
  108. package/lib/virtual/utils/shallowCompare.js +14 -0
  109. package/package.json +8 -3
  110. package/src/components/container/theme/Container.base.theme.ts +1 -1
  111. package/src/components/divider/theme/Divider.base.theme.ts +1 -1
  112. package/src/components/input/Input.vue +1 -2
  113. package/src/components/select/Select.vue +97 -20
  114. package/src/components/table/Table.vue +1 -1
  115. package/src/composables/useVirtualList.ts +1 -1
  116. package/src/index.ts +1 -0
  117. package/src/install.ts +9 -3
  118. package/src/version.ts +1 -1
  119. package/src/virtual/README.md +285 -0
  120. package/src/virtual/components/infiniteLoader/InfiniteLoader.test.ts +96 -0
  121. package/src/virtual/components/infiniteLoader/InfiniteLoader.vue +18 -0
  122. package/src/virtual/components/virtualGrid/VirtualGrid.vue +322 -0
  123. package/src/virtual/components/virtualGrid/types.ts +160 -0
  124. package/src/virtual/components/virtualList/VirtualList.test.ts +164 -0
  125. package/src/virtual/components/virtualList/VirtualList.vue +227 -0
  126. package/src/virtual/components/virtualList/isDynamicRowHeight.ts +13 -0
  127. package/src/virtual/components/virtualList/types.ts +127 -0
  128. package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +197 -0
  129. package/src/virtual/components/virtualList/useDynamicRowHeight.ts +149 -0
  130. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.test.ts +141 -0
  131. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.ts +82 -0
  132. package/src/virtual/composables/infinite-loader/types.ts +36 -0
  133. package/src/virtual/composables/infinite-loader/useInfiniteLoader.test.ts +236 -0
  134. package/src/virtual/composables/infinite-loader/useInfiniteLoader.ts +88 -0
  135. package/src/virtual/core/createCachedBounds.ts +72 -0
  136. package/src/virtual/core/getEstimatedSize.ts +29 -0
  137. package/src/virtual/core/getOffsetForIndex.ts +90 -0
  138. package/src/virtual/core/getStartStopIndices.test.ts +45 -0
  139. package/src/virtual/core/getStartStopIndices.ts +71 -0
  140. package/src/virtual/core/types.ts +17 -0
  141. package/src/virtual/core/useCachedBounds.ts +21 -0
  142. package/src/virtual/core/useIsRtl.ts +25 -0
  143. package/src/virtual/core/useItemSize.ts +34 -0
  144. package/src/virtual/core/useVirtualizer.ts +294 -0
  145. package/src/virtual/index.ts +25 -0
  146. package/src/virtual/test-utils/mockResizeObserver.ts +162 -0
  147. package/src/virtual/types.ts +3 -0
  148. package/src/virtual/utils/adjustScrollOffsetForRtl.ts +37 -0
  149. package/src/virtual/utils/areArraysEqual.ts +13 -0
  150. package/src/virtual/utils/assert.ts +10 -0
  151. package/src/virtual/utils/getRTLOffsetType.ts +51 -0
  152. package/src/virtual/utils/getScrollbarSize.ts +24 -0
  153. package/src/virtual/utils/isRtl.ts +13 -0
  154. package/src/virtual/utils/parseNumericStyleValue.ts +21 -0
  155. package/src/virtual/utils/shallowCompare.ts +29 -0
  156. package/volar.d.ts +3 -0
@@ -1,6 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref } from 'vue'
3
3
  import { useColors, useNotifications, colors as TailwindColors } from '@indielayer/ui'
4
+ import { useDocMeta } from '../composables/useDocMeta'
5
+
6
+ useDocMeta({
7
+ title: 'Colors',
8
+ description: 'Color palette, semantic tokens, and color props on Indielayer UI components.',
9
+ path: '/colors',
10
+ })
4
11
 
5
12
  const notifications = useNotifications()
6
13
  const { getPalette } = useColors()
@@ -23,44 +30,45 @@ function copyPalette(val: any) {
23
30
  </script>
24
31
 
25
32
  <template>
26
- <h1 class="text-4xl font-semibold">Colors</h1>
27
- <h2 class="text-lg my-2 text-gray-500 dark:text-gray-400">Generate your own palettes, use Tailwind's or use color prop to generate at runtime</h2>
28
- <x-divider class="mt-4 mb-8"/>
29
-
30
- <h2 class="text-2xl mb-2">Generate your own palettes</h2>
31
- <p class="mb-8 text-gray-500 dark:text-gray-400">Generate your own color palette like Tailwind. With the same properties as Tailwind color palettes.</p>
32
- <p class="font-medium"></p>
33
- <div class="mt-4">
34
- <x-input v-model="color500" label="Change here the middle value (500) to generate a new palette"/>
35
- </div>
36
- <div class="grid grid-cols-11 my-4 text-sm">
37
- <div v-for="shade in Object.keys(palette)" :key="shade" class="h-32 flex items-center justify-center" :style="`background-color: ${palette[shade]}`">{{ shade }}</div>
38
- </div>
33
+ <div class="docs-container">
34
+ <h1 class="text-4xl font-semibold">Colors</h1>
35
+ <h2 class="text-lg my-2 text-gray-500 dark:text-gray-400">Generate your own palettes, use Tailwind's or use color prop to generate at runtime</h2>
36
+ <x-divider class="mt-4 mb-8"/>
37
+
38
+ <h2 class="text-2xl mb-2">Generate your own palettes</h2>
39
+ <p class="mb-8 text-gray-500 dark:text-gray-400">Generate your own color palette like Tailwind. With the same properties as Tailwind color palettes.</p>
40
+ <p class="font-medium"></p>
41
+ <div class="mt-4">
42
+ <x-input v-model="color500" label="Change here the middle value (500) to generate a new palette"/>
43
+ </div>
44
+ <div class="grid grid-cols-11 my-4 text-sm">
45
+ <div v-for="shade in Object.keys(palette)" :key="shade" class="h-32 flex items-center justify-center" :style="`background-color: ${palette[shade]}`">{{ shade }}</div>
46
+ </div>
39
47
 
40
- <x-button icon-left="copy" :color="color500" block @click="copyPalette(palette)">Copy Palette</x-button>
48
+ <x-button icon-left="copy" :color="color500" block @click="copyPalette(palette)">Copy Palette</x-button>
41
49
 
42
- <p class="text-gray-500 dark:text-gray-400 mt-4">This is the generated palette when you use the color directly on the color prop like &#x3C;x-button <b>color="#10B981"</b>&#x3E;Hello&#x3C;/x-button&#x3E;</p>
50
+ <p class="text-gray-500 dark:text-gray-400 mt-4">This is the generated palette when you use the color directly on the color prop like &#x3C;x-button <b>color="#10B981"</b>&#x3E;Hello&#x3C;/x-button&#x3E;</p>
43
51
 
44
- <x-divider class="mt-4 mb-8"/>
52
+ <x-divider class="mt-4 mb-8"/>
45
53
 
46
- <h2 class="text-2xl mb-2">Theme colors</h2>
47
- <p class="mb-8 text-gray-500 dark:text-gray-400">Default theme color names and how to customize them.</p>
54
+ <h2 class="text-2xl mb-2">Theme colors</h2>
55
+ <p class="mb-8 text-gray-500 dark:text-gray-400">Default theme color names and how to customize them.</p>
48
56
 
49
- <p>The default theme color names are
50
- <b class="text-primary-500">primary</b>,
51
- <b class="text-secondary-500">secondary</b>,
52
- <b class="text-success-500">success</b>,
53
- <b class="text-warning-500">warning</b> and
54
- <b class="text-error-500">error</b>.
55
- You can customize them or even add your own.
56
- </p>
57
+ <p>The default theme color names are
58
+ <b class="text-primary-500">primary</b>,
59
+ <b class="text-secondary-500">secondary</b>,
60
+ <b class="text-success-500">success</b>,
61
+ <b class="text-warning-500">warning</b> and
62
+ <b class="text-error-500">error</b>.
63
+ You can customize them or even add your own.
64
+ </p>
57
65
 
58
- <p class="my-4"></p>
66
+ <p class="my-4"></p>
59
67
 
60
- <p class="my-2">Customize the color prop, e.g. <b>color="primary"</b> on the UI initialization.</p>
61
- <code-snippet
62
- lang="js"
63
- :code="`import { createApp } from 'vue'
68
+ <p class="my-2">Customize the color prop, e.g. <b>color="primary"</b> on the UI initialization.</p>
69
+ <code-snippet
70
+ lang="js"
71
+ :code="`import { createApp } from 'vue'
64
72
  import UI, { colors } from '@indielayer/ui'
65
73
 
66
74
  const app = createApp(App)
@@ -76,14 +84,14 @@ app.use(UI, {
76
84
  },
77
85
  }
78
86
  })`"
79
- />
87
+ />
80
88
 
81
- <p class="my-4">Allow you to use the Tailwind classes like <b>text-primary-500</b>, <b>bg-primary-500</b>, etc:</p>
89
+ <p class="my-4">Allow you to use the Tailwind classes like <b>text-primary-500</b>, <b>bg-primary-500</b>, etc:</p>
82
90
 
83
- <div>
84
- <code-snippet
85
- lang="js"
86
- :code="`// tailwind.config.js
91
+ <div>
92
+ <code-snippet
93
+ lang="js"
94
+ :code="`// tailwind.config.js
87
95
  //...
88
96
  const colors = require('tailwindcss/colors')
89
97
 
@@ -101,19 +109,20 @@ module.exports = {
101
109
  },
102
110
  },
103
111
  }`"
104
- />
105
- </div>
112
+ />
113
+ </div>
106
114
 
107
- <x-divider class="mt-4 mb-8"/>
115
+ <x-divider class="mt-4 mb-8"/>
108
116
 
109
- <h2 class="text-2xl mb-2">Tailwind Colors</h2>
110
- <p class="mb-8 text-gray-500 dark:text-gray-400">Default tailwind color palettes.</p>
117
+ <h2 class="text-2xl mb-2">Tailwind Colors</h2>
118
+ <p class="mb-8 text-gray-500 dark:text-gray-400">Default tailwind color palettes.</p>
111
119
 
112
- <div class="grid gap-6 grid-cols-1 md:grid-cols-2 text-xs">
113
- <div v-for="(pal, key) in TailwindColors" :key="key">
114
- <div class="text-overline mb-2">{{ key }}</div>
115
- <div class="grid grid-cols-11">
116
- <div v-for="(color, palKey) in pal" :key="palKey" class="h-32 flex items-center justify-center" :style="`background-color: ${color}`">{{ palKey }}</div>
120
+ <div class="grid gap-6 grid-cols-1 md:grid-cols-2 text-xs">
121
+ <div v-for="(pal, key) in TailwindColors" :key="key">
122
+ <div class="text-overline mb-2">{{ key }}</div>
123
+ <div class="grid grid-cols-11">
124
+ <div v-for="(color, palKey) in pal" :key="palKey" class="h-32 flex items-center justify-center" :style="`background-color: ${color}`">{{ palKey }}</div>
125
+ </div>
117
126
  </div>
118
127
  </div>
119
128
  </div>
@@ -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>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref } from 'vue'
3
3
 
4
- const selected = ref<undefined | string>()
4
+ const selected = ref<string>()
5
5
  const options = ref([
6
6
  { value: 'A', label: 'Option A' },
7
7
  { value: 'B', label: 'Option B' },
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { ref } from 'vue'
3
+ import type { SelectOption } from '@indielayer/ui'
3
4
 
4
- const selected = ref<undefined | string>()
5
- const selected2 = ref<undefined | string>()
6
- const options = ref([
5
+ const selected = ref<string>()
6
+ const selected2 = ref<string>()
7
+ const options = ref<SelectOption[]>([
7
8
  { value: 'A', label: 'Option A' },
8
9
  { value: 'B', label: 'Option B' },
9
10
  { value: 'C', label: 'Option C' },
@@ -11,16 +12,16 @@ const options = ref([
11
12
 
12
13
  // function to generate
13
14
  function genOptions(x: number) {
14
- const options = []
15
+ const options: SelectOption[] = []
15
16
 
16
17
  for (let i = 0; i < x; i++) {
17
- options.push({ value: i.toString(), label: 'Option ' + i, suffix: i })
18
+ options.push({ value: i.toString(), label: 'Option ' + i, prefix: 'bg-green-500', suffix: 'dude' + i.toString() })
18
19
  }
19
20
 
20
21
  return options
21
22
  }
22
23
 
23
- const options2 = ref(genOptions(1000))
24
+ const options2 = ref<SelectOption[]>(genOptions(1000))
24
25
  </script>
25
26
 
26
27
  <template>
@@ -39,13 +40,18 @@ const options2 = ref(genOptions(1000))
39
40
  label="Filterable - virtual list"
40
41
  placeholder="Placeholder"
41
42
  filterable
43
+ filterable-prefix
44
+ filterable-suffix
42
45
  clearable
43
46
  virtual-list
44
47
  :virtual-list-item-height="33"
45
48
  :options="options2"
46
49
  >
50
+ <template #prefix="{ item }">
51
+ <div class="w-2 h-2 shrink-0 rounded-full text-xs" :class="item.prefix"></div>
52
+ </template>
47
53
  <template #suffix="{ item }">
48
- <span class="text-secondary-400 text-xs font-mono w-2">#{{ item.suffix }}</span>
54
+ <span class="text-secondary-400 text-xs font-mono">#{{ item.suffix }}</span>
49
55
  </template>
50
56
  </x-select>
51
57
  <x-select
@@ -57,6 +63,7 @@ const options2 = ref(genOptions(1000))
57
63
  >
58
64
  <template #input="{ popover, label, disabled }">
59
65
  <button
66
+ type="button"
60
67
  class="w-full text-left border rounded-md px-3 py-2"
61
68
  :disabled="disabled"
62
69
  @click="popover?.show()"
@@ -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>