@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
@@ -0,0 +1,157 @@
1
+ <script setup lang="ts">
2
+ import { onBeforeUnmount, ref } from 'vue'
3
+
4
+ const selected = ref('A')
5
+ const options = [{
6
+ value: 'A',
7
+ label: 'Option A',
8
+ }, {
9
+ value: 'B',
10
+ label: 'Option B',
11
+ }]
12
+ const toggle = ref(false)
13
+ const tab = ref('A')
14
+ const radio = ref('a')
15
+ const password = ref('')
16
+ const slider = ref(80)
17
+ const isPlaying = ref(false)
18
+ let interval: ReturnType<typeof setInterval> | null = null
19
+
20
+ function togglePlayer() {
21
+ if (isPlaying.value) {
22
+ if (interval) clearInterval(interval)
23
+ interval = null
24
+ } else {
25
+ interval = setInterval(() => {
26
+ slider.value += 1
27
+ }, 1000)
28
+ }
29
+
30
+ isPlaying.value = !isPlaying.value
31
+ }
32
+
33
+ onBeforeUnmount(() => {
34
+ if (interval) clearInterval(interval)
35
+ })
36
+ </script>
37
+
38
+ <template>
39
+ <div class="grid gap-6 grid-cols-1 xl:grid-cols-2 max-w-4xl">
40
+ <div>
41
+ <div class="flex items-center">
42
+ <x-button-group>
43
+ <x-button icon="menu-alt-1"/>
44
+ <x-button icon="menu"/>
45
+ <x-button icon="menu-alt-3"/>
46
+ </x-button-group>
47
+
48
+ <x-spacer/>
49
+
50
+ <x-select
51
+ v-model="selected"
52
+ :options="options"
53
+ placeholder="Placeholder"
54
+ class="w-64 ml-2"
55
+ hide-footer
56
+ />
57
+ </div>
58
+ <x-card class="p-4 flex items-center mt-4">
59
+ <x-badge outlined position="bottom">
60
+ <x-avatar image="https://avatars.githubusercontent.com/u/3942799?v=4" rounded/>
61
+ </x-badge>
62
+ <div class="ml-4">
63
+ <div class="font-bold">John Smith</div>
64
+ <div class="text-gray-400 text-sm">jsmith@indielayer.com</div>
65
+ </div>
66
+ </x-card>
67
+ <div class="flex space-x-2 mt-4">
68
+ <x-toggle v-model="toggle"/>
69
+ <x-spacer/>
70
+ <x-tag rounded size="sm" :color="toggle ? 'primary' : undefined">{{ toggle ? 'Active' : 'Inactive' }}</x-tag>
71
+ </div>
72
+ <div class="mt-4">
73
+ <div class="grid grid-cols-3 gap-2">
74
+ <x-button color="primary">Filled</x-button>
75
+ <x-button color="primary" outlined>Outlined</x-button>
76
+ <x-button color="primary" light>Light</x-button>
77
+ </div>
78
+ </div>
79
+ <x-card flat class="border mt-4">
80
+ <div class="p-2 flex items-center">
81
+ <x-avatar image="https://avatars.githubusercontent.com/u/3942799?v=4" size="lg"/>
82
+ <div class="ml-4">
83
+ <div class="font-bold">Holding back</div>
84
+ <div class="text-gray-400 text-sm">BANKS</div>
85
+ </div>
86
+ </div>
87
+ <x-divider/>
88
+ <div class="p-6">
89
+ <div class="flex items-center justify-center mb-6">
90
+ <x-button icon="rewind" ghost rounded/>
91
+ <x-button
92
+ :icon="isPlaying ? 'pause' : 'play'"
93
+ rounded
94
+ size="lg"
95
+ class="mx-2"
96
+ @click="togglePlayer"
97
+ />
98
+ <x-button icon="forward" ghost rounded/>
99
+ </div>
100
+ <x-slider v-model="slider" class="w-full">
101
+ <template #prefix>
102
+ <div class="w-12 text-sm text-left">3:26</div>
103
+ </template>
104
+ <template #suffix>
105
+ <div class="w-12 text-sm text-right">4:12</div>
106
+ </template>
107
+ </x-slider>
108
+ </div>
109
+ </x-card>
110
+ </div>
111
+ <div>
112
+ <x-tab-group v-model="tab" grow variant="block">
113
+ <x-tab label="Profile" value="A"/>
114
+ <x-tab label="Settings" value="B"/>
115
+ <x-tab label="Content" value="C"/>
116
+ </x-tab-group>
117
+ <div class="mt-3 mb-1">
118
+ <x-radio
119
+ v-model="radio"
120
+ value="a"
121
+ label="Credit card"
122
+ class="border p-3 rounded w-full"
123
+ :class="{ 'bg-emerald-50 dark:bg-gray-800 border-emerald-400 dark:border-emerald-800': radio === 'a'}"
124
+ >
125
+ <div class="text-gray-400 font-light text-sm">VISA · · · · 2592</div>
126
+ </x-radio>
127
+ </div>
128
+ <div>
129
+ <x-radio
130
+ v-model="radio"
131
+ value="b"
132
+ label="Bank account"
133
+ class="border p-3 rounded w-full"
134
+ :class="{ 'bg-emerald-50 dark:bg-gray-800 border-emerald-400 dark:border-emerald-800': radio === 'b'}"
135
+ >
136
+ <div class="text-gray-400 font-light text-sm">Santander · · · · 1580</div>
137
+ </x-radio>
138
+ </div>
139
+ <div class="my-3">
140
+ <x-alert type="success" color="success" outlined>User details updated!</x-alert>
141
+ </div>
142
+ <div>
143
+ <x-form :auto-focus="false">
144
+ <x-input label="Email" type="email" block/>
145
+ <x-input
146
+ v-model="password"
147
+ label="Password"
148
+ type="password"
149
+ show-password-toggle
150
+ block
151
+ />
152
+ <x-checkbox label="Keep me logged in"/>
153
+ </x-form>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </template>
@@ -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
  ]
@@ -2,6 +2,7 @@
2
2
  import { inject, ref, unref, watch } from 'vue'
3
3
  import { version, type UITheme } from '@indielayer/ui'
4
4
  import ToolbarSearch from './ToolbarSearch.vue'
5
+ import ToolbarColorToggle from './ToolbarColorToggle.vue'
5
6
 
6
7
  const selectTheme = inject('selectTheme', {
7
8
  theme: {} as UITheme,
@@ -25,7 +26,7 @@ const isDev = import.meta.env.DEV
25
26
  </script>
26
27
 
27
28
  <template>
28
- <x-container fluid class="bg-white dark:bg-secondary-800">
29
+ <x-container fluid class="bg-white dark:bg-slate-800/60">
29
30
  <div class="flex justify-items-center items-center py-2 min-h-[54px]">
30
31
  <a href="/" class="flex items-center">
31
32
  <img src="@/assets/images/logo_mini.svg" width="26" alt="Indielayer"/>
@@ -40,7 +41,15 @@ const isDev = import.meta.env.DEV
40
41
 
41
42
  <x-spacer/>
42
43
 
43
- <div class="flex items-center font-semibold text-sm">
44
+ <div class="flex items-center font-semibold text-sm gap-1">
45
+ <x-select
46
+ v-if="isDev"
47
+ v-model="selected"
48
+ :options="options"
49
+ size="xs"
50
+ class="hidden md:block w-28"
51
+ />
52
+ <toolbar-color-toggle class="hidden sm:flex" />
44
53
  <toolbar-search />
45
54
  <x-divider vertical class="!h-2 px-2"/>
46
55
  <div class="tracking-wide text-xs">v{{ version }}</div>
@@ -8,10 +8,10 @@ try {
8
8
 
9
9
  if (storedMode) {
10
10
  colorMode.value = storedMode
11
- // } else {
12
- // if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
13
- // colorMode.value = 'dark'
14
- // }
11
+ } else {
12
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
13
+ colorMode.value = 'dark'
14
+ }
15
15
  }
16
16
  } catch (e) {
17
17
  colorMode.value = 'light'
@@ -2,10 +2,17 @@
2
2
  import { useEventListener, useMouse } from '@vueuse/core'
3
3
  import { computed, ref, watch, nextTick, onMounted } from 'vue'
4
4
  import Fuse from 'fuse.js'
5
- import ComponentsSearchIndex from '../../search/components.json'
5
+ import SearchIndex from '../../search/index.json'
6
6
  import type { XInput } from 'src'
7
7
 
8
- const fuse = new Fuse(ComponentsSearchIndex, {
8
+ type SearchItem = {
9
+ name: string;
10
+ description: string;
11
+ url: string;
12
+ category: 'guide' | 'component';
13
+ }
14
+
15
+ const fuse = new Fuse(SearchIndex as SearchItem[], {
9
16
  keys: [{
10
17
  name: 'name',
11
18
  weight: 2,
@@ -17,65 +24,63 @@ const fuse = new Fuse(ComponentsSearchIndex, {
17
24
  })
18
25
 
19
26
  type FuseResult = {
20
- item: {
21
- name: string;
22
- description: string;
23
- url: string;
24
- };
27
+ item: SearchItem;
25
28
  refIndex: number;
26
29
  score: number;
27
30
  }
28
31
 
29
32
  const isModalOpen = ref(false)
30
33
  const searchInput = ref('')
31
- const ui = ref<FuseResult[]>([])
34
+ const results = ref<FuseResult[]>([])
32
35
  const searchList = ref<HTMLDivElement>()
33
- const results = ref<HTMLLIElement[]>([])
36
+ const resultElements = ref<HTMLLIElement[]>([])
34
37
  const selectedIndex = ref<number>(-1)
35
38
 
36
39
  const inputEl = ref<InstanceType<typeof XInput> | null>(null)
37
40
 
38
- watch(isModalOpen, (newValue, oldValue) => {
41
+ const searchSections = computed(() => {
42
+ const guides = results.value.filter((r) => r.item.category === 'guide')
43
+ const components = results.value.filter((r) => r.item.category === 'component')
44
+
45
+ return [
46
+ { key: 'component', label: 'Components', items: components },
47
+ { key: 'guide', label: 'Guides', items: guides },
48
+ ].filter((s) => s.items.length > 0)
49
+ })
50
+
51
+ watch(isModalOpen, (newValue) => {
39
52
  setTimeout(() => {
40
53
  if (newValue) inputEl.value?.focus()
41
54
  }, 100)
42
55
  })
43
56
 
44
- const searchSections = computed(() => [
45
- { key:'ui', label: 'UI', items: ui.value },
46
- ])
47
-
48
57
  function clearSearch() {
49
58
  selectedIndex.value = -1
50
- ui.value = []
59
+ results.value = []
51
60
  }
52
61
 
53
62
  function openSearch() {
54
63
  clearSearch()
55
-
56
64
  searchInput.value = ''
57
65
  isModalOpen.value = true
58
66
  }
59
67
 
60
68
  function selectItem(item: HTMLLIElement, index?: number) {
61
- if (!item) {
62
- return
63
- }
64
-
65
- results.value?.[selectedIndex.value]?.setAttribute('aria-selected', 'false')
69
+ if (!item) return
66
70
 
71
+ resultElements.value?.[selectedIndex.value]?.setAttribute('aria-selected', 'false')
67
72
  item.setAttribute('aria-selected', 'true')
68
- item.scrollIntoView({ 'block':'nearest' })
73
+ item.scrollIntoView({ block: 'nearest' })
69
74
 
70
- if (!index) {
71
- index = results.value.findIndex(({ id }) => id === item.id)
75
+ if (index === undefined) {
76
+ index = resultElements.value.findIndex(({ id }) => id === item.id)
72
77
  }
73
78
 
74
79
  selectedIndex.value = index
75
80
  }
76
81
 
77
82
  function selectItemByIndex(index: number) {
78
- selectItem(results.value?.[index], index)
83
+ selectItem(resultElements.value?.[index], index)
79
84
  }
80
85
 
81
86
  function selectFirstItem() {
@@ -83,39 +88,29 @@ function selectFirstItem() {
83
88
  }
84
89
 
85
90
  function selectLastItem() {
86
- selectItemByIndex(results.value.length - 1)
91
+ selectItemByIndex(resultElements.value.length - 1)
87
92
  }
88
93
 
89
94
  function selectNextItem() {
90
- const totalResults = results.value.length
95
+ const total = resultElements.value.length
91
96
 
92
- if (totalResults <= 0) {
93
- return
94
- }
97
+ if (total <= 0) return
95
98
 
96
99
  const nextIndex = selectedIndex.value + 1
97
100
 
98
- if (nextIndex >= totalResults) {
99
- selectFirstItem()
100
- } else {
101
- selectItemByIndex(nextIndex)
102
- }
101
+ if (nextIndex >= total) selectFirstItem()
102
+ else selectItemByIndex(nextIndex)
103
103
  }
104
104
 
105
105
  function selectPreviousItem() {
106
- const totalResults = results.value.length
106
+ const total = resultElements.value.length
107
107
 
108
- if (totalResults <= 0) {
109
- return
110
- }
108
+ if (total <= 0) return
111
109
 
112
110
  const previousIndex = selectedIndex.value - 1
113
111
 
114
- if (previousIndex >= 0) {
115
- selectItemByIndex(previousIndex)
116
- } else {
117
- selectLastItem()
118
- }
112
+ if (previousIndex >= 0) selectItemByIndex(previousIndex)
113
+ else selectLastItem()
119
114
  }
120
115
 
121
116
  function keydownInput(e: KeyboardEvent) {
@@ -130,7 +125,7 @@ function keydownInput(e: KeyboardEvent) {
130
125
  }
131
126
 
132
127
  if (e.key === 'Enter') {
133
- const item = results.value?.[selectedIndex.value]
128
+ const item = resultElements.value?.[selectedIndex.value]
134
129
 
135
130
  if (item) {
136
131
  (item.firstElementChild as HTMLLinkElement)?.click()
@@ -142,37 +137,38 @@ const { x: mouseX, y: mouseY } = useMouse({ type: 'page' })
142
137
 
143
138
  function hoverResult(e: MouseEvent) {
144
139
  if (mouseX.value !== e.x || mouseY.value !== e.y) {
145
- selectItem(e.target as HTMLLIElement)
140
+ selectItem(e.currentTarget as HTMLLIElement)
146
141
  }
147
142
  }
148
143
 
149
144
  function searchIndexes() {
150
- ui.value = fuse.search(searchInput.value, {
151
- limit: 10,
152
- }) as FuseResult[]
145
+ if (!searchInput.value.trim()) {
146
+ results.value = []
147
+
148
+ return
149
+ }
150
+
151
+ results.value = fuse.search(searchInput.value, { limit: 12 }) as FuseResult[]
153
152
  }
154
153
 
155
154
  watch(searchSections, async () => {
156
- results.value = []
157
-
155
+ resultElements.value = []
158
156
  await nextTick()
159
157
 
160
158
  const items = searchList.value?.querySelectorAll('[data-name="list-item"]')
161
159
 
162
160
  items?.forEach((el) => {
163
161
  el.setAttribute('aria-selected', 'false')
164
- results.value.push(el as HTMLLIElement)
162
+ resultElements.value.push(el as HTMLLIElement)
165
163
  })
166
164
 
167
- setTimeout(() => selectFirstItem())
165
+ if (resultElements.value.length) setTimeout(() => selectFirstItem())
168
166
  })
169
167
 
170
168
  const metaKey = ref('')
171
169
 
172
170
  onMounted(() => {
173
- metaKey.value = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)
174
- ? '⌘'
175
- : 'Ctrl'
171
+ metaKey.value = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl'
176
172
  })
177
173
 
178
174
  if (document) {
@@ -206,7 +202,7 @@ if (document) {
206
202
  v-model="searchInput"
207
203
  type="search"
208
204
  aria-controls="search-list"
209
- placeholder="Search components"
205
+ placeholder="Search docs and components"
210
206
  hide-footer
211
207
  icon-left="search"
212
208
  @input="searchIndexes"
@@ -214,10 +210,10 @@ if (document) {
214
210
  />
215
211
  </div>
216
212
  </template>
217
- <div id="search-list" ref="searchList">
213
+ <div id="search-list" ref="searchList" class="max-h-96 overflow-y-auto px-2">
218
214
  <template v-for="section in searchSections" :key="section.key">
219
- <section v-if="section.items.length > 0">
220
- <!-- <x-divider :id="`${section.key}-label`" class="my-2" :label="section.label" /> -->
215
+ <section v-if="section.items.length > 0" class="mb-2">
216
+ <x-divider :label="section.label" class="my-2" />
221
217
  <ul role="listbox" :aria-labelledby="`${section.key}-label`">
222
218
  <li
223
219
  v-for="(result) in section.items"
@@ -225,7 +221,7 @@ if (document) {
225
221
  :key="result.item.url"
226
222
  data-name="list-item"
227
223
  role="option"
228
- class="aria-selected:bg-secondary-100 dark:aria-selected:bg-secondary-800 rounded p-2 mb-2"
224
+ class="aria-selected:bg-secondary-100 dark:aria-selected:bg-secondary-800 rounded p-2 mb-1"
229
225
  @mouseenter="hoverResult"
230
226
  >
231
227
  <x-link
@@ -234,12 +230,13 @@ if (document) {
234
230
  @click="isModalOpen = false"
235
231
  >
236
232
  <p class="text-base w-full mb-1 mt-0 font-bold">{{ result.item.name }}</p>
237
- <p class="text-sm m-0">{{ result.item.description }}</p>
233
+ <p class="text-sm m-0 text-gray-500 dark:text-gray-400 line-clamp-2">{{ result.item.description }}</p>
238
234
  </x-link>
239
235
  </li>
240
236
  </ul>
241
237
  </section>
242
238
  </template>
239
+ <p v-if="searchInput && !results.length" class="text-center text-gray-500 py-8 text-sm">No results found.</p>
243
240
  </div>
244
241
 
245
242
  <template #actions>
@@ -0,0 +1,47 @@
1
+ import { useHead } from '@unhead/vue'
2
+
3
+ const SITE_URL = 'https://indielayer.com'
4
+ const SITE_NAME = 'Indielayer'
5
+ const DEFAULT_OG_IMAGE = `${SITE_URL}/card.jpg`
6
+ const DEFAULT_KEYWORDS = 'indielayer, vue, vue 3, nuxt, nuxt 3, tailwind css, ui kit, ui library, ui components, component library'
7
+
8
+ export function useDocMeta(options: {
9
+ title: string;
10
+ description?: string;
11
+ path?: string;
12
+ }) {
13
+ const pageTitle = options.title === 'Indielayer UI'
14
+ ? 'Indielayer UI — Vue 3 component library'
15
+ : `${options.title} — Indielayer UI`
16
+
17
+ const description = options.description
18
+ || 'Vue 3 and Tailwind CSS UI components for fast web applications.'
19
+ const url = options.path ? `${SITE_URL}${options.path}` : SITE_URL
20
+
21
+ useHead({
22
+ title: pageTitle,
23
+ meta: [
24
+ { name: 'description', content: description },
25
+ { name: 'keywords', content: DEFAULT_KEYWORDS },
26
+ { property: 'og:site_name', content: SITE_NAME },
27
+ { property: 'og:title', content: pageTitle },
28
+ { property: 'og:description', content: description },
29
+ { property: 'og:url', content: url },
30
+ { property: 'og:type', content: 'website' },
31
+ { property: 'og:image', content: DEFAULT_OG_IMAGE },
32
+ { property: 'og:image:width', content: '1440' },
33
+ { property: 'og:image:height', content: '731' },
34
+ { property: 'og:image:type', content: 'image/jpeg' },
35
+ { property: 'og:image:alt', content: SITE_NAME },
36
+ { name: 'twitter:card', content: 'summary_large_image' },
37
+ { name: 'twitter:site', content: '@indielayer' },
38
+ { name: 'twitter:image', content: DEFAULT_OG_IMAGE },
39
+ { name: 'twitter:image:alt', content: SITE_NAME },
40
+ { name: 'twitter:title', content: pageTitle },
41
+ { name: 'twitter:description', content: description },
42
+ ],
43
+ link: [
44
+ { rel: 'canonical', href: url },
45
+ ],
46
+ })
47
+ }
package/docs/icons.ts CHANGED
@@ -28,6 +28,34 @@ export default {
28
28
  moon: '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>',
29
29
  upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line>',
30
30
  menu: '<line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line>',
31
+ 'menu-alt-1': '<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h7"/>',
32
+ 'menu-alt-3': '<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16m-7 6h7" />',
33
+ rewind: {
34
+ icon: '<polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon>',
35
+ filled: true,
36
+ },
37
+ play: {
38
+ icon: '<polygon points="5 3 19 12 5 21 5 3"></polygon>',
39
+ filled: true,
40
+ },
41
+ pause: {
42
+ icon: '<rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect>',
43
+ filled: true,
44
+ },
45
+ forward: {
46
+ icon: '<polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon>',
47
+ filled: true,
48
+ },
49
+ vue: {
50
+ icon: '<path d="M17.2322 0.000135422L13.9992 5.77351L10.7661 0.000135422H-0.000488281L13.9992 24.9999L27.9988 0.000135422H17.2322Z" fill="#41B883"/><path d="M17.2322 0.000144958L13.9992 5.77352L10.7661 0.000144958H5.59937L13.9992 14.9997L22.3989 0.000144958H17.2322Z" fill="#34495E"/>',
51
+ filled: true,
52
+ viewBox: '0 0 28 25',
53
+ },
54
+ nuxt: {
55
+ icon: '<path fill-rule="evenodd" clip-rule="evenodd" d="M15.6348 2.10272C14.6734 0.449622 12.2697 0.449592 11.3083 2.10272L0.830082 20.1192C-0.131361 21.7723 1.07044 23.8387 2.99332 23.8387H11.1732C10.3515 23.1206 10.0473 21.8784 10.6691 20.8126L18.6048 7.2094L15.6348 2.10272Z" fill="#80EEC0"/><path d="M22.2744 6.05476C23.0701 4.7022 25.0593 4.7022 25.855 6.05476L34.5264 20.7955C35.3222 22.148 34.3277 23.8387 32.7364 23.8387H15.3931C13.8017 23.8387 12.8071 22.148 13.6028 20.7955L22.2744 6.05476Z" fill="#00DC82"/>',
56
+ filled: true,
57
+ viewBox: '0 0 35 24',
58
+ },
31
59
  bold: '<path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6z" /><path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" />',
32
60
  italic: '<path stroke="none" d="M0 0h24v24H0z" fill="none"></path><line x1="11" y1="5" x2="17" y2="5"></line><line x1="7" y1="19" x2="13" y2="19"></line><line x1="14" y1="5" x2="10" y2="19"></line>',
33
61
  'edit': '<path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>',
@@ -24,9 +24,7 @@ const drawer = ref(false)
24
24
  </x-drawer>
25
25
  <x-notifications>
26
26
  <div id="main" class="flex-1 overflow-y-scroll bg-secondary-50 dark:bg-gray-900">
27
- <x-container class="py-4 lg:py-8" tag="section">
28
- <router-view />
29
- </x-container>
27
+ <router-view />
30
28
  </div>
31
29
  </x-notifications>
32
30
  </div>
@@ -1,3 +1,5 @@
1
1
  <template>
2
- <router-view />
2
+ <div class="docs-container">
3
+ <router-view />
4
+ </div>
3
5
  </template>
package/docs/main.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createApp } from 'vue'
2
+ import { createHead } from '@unhead/vue/client'
2
3
  import UI, { BaseTheme } from '@indielayer/ui'
3
4
  import App from './App.vue'
4
5
  import router from './router'
@@ -10,12 +11,15 @@ import CodeSnippet from './components/common/CodeSnippet.vue'
10
11
  import MultiSnippet from './components/common/MultiSnippet.vue'
11
12
  import CodePreview from './components/common/CodePreview.vue'
12
13
  import DocumentPage from './components/common/DocumentPage.vue'
14
+ import DocsHero from './components/common/DocsHero.vue'
13
15
 
14
16
  // css
15
17
  import './assets/css/tailwind.css'
16
18
 
17
19
  const app = createApp(App)
20
+ const head = createHead()
18
21
 
22
+ app.use(head)
19
23
  app.use(UI, {
20
24
  prefix: 'X',
21
25
  icons,
@@ -29,5 +33,6 @@ app.component('CodeSnippet', CodeSnippet)
29
33
  app.component('MultiSnippet', MultiSnippet)
30
34
  app.component('CodePreview', CodePreview)
31
35
  app.component('DocumentPage', DocumentPage)
36
+ app.component('DocsHero', DocsHero)
32
37
 
33
38
  app.mount('#app')