diff --git a/.gitignore b/.gitignore
index d9ed9cb24..49f145962 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ docs/.vitepress/data/iconNodes
docs/.vitepress/data/iconMetaData.ts
docs/.vitepress/data/releaseMetaData.json
docs/.vitepress/data/releaseMetaData
+docs/.vitepress/data/iconDetails
docs/.vitepress/data/relatedIcons.json
docs/.vercel
docs/.nitro
diff --git a/docs/.vitepress/api/categories/index.get.ts b/docs/.vitepress/api/categories/index.get.ts
new file mode 100644
index 000000000..a1fb097ca
--- /dev/null
+++ b/docs/.vitepress/api/categories/index.get.ts
@@ -0,0 +1,11 @@
+import { eventHandler, setResponseHeader } from 'h3'
+import iconMetaData from '../../data/iconMetaData'
+
+export default eventHandler((event) => {
+ setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
+ setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
+
+ return Object.fromEntries(
+ Object.entries(iconMetaData).map(([name, { categories }]) => [ name, categories ])
+ )
+})
diff --git a/docs/.vitepress/api/icon-nodes/index.get.ts b/docs/.vitepress/api/icon-nodes/index.get.ts
index e07168676..a8623661c 100644
--- a/docs/.vitepress/api/icon-nodes/index.get.ts
+++ b/docs/.vitepress/api/icon-nodes/index.get.ts
@@ -8,6 +8,7 @@ export default eventHandler((event) => {
const withUniqueKeys = query.withUniqueKeys === 'true'
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
+ setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
if (withUniqueKeys) {
return iconNodes
diff --git a/docs/.vitepress/api/icons/[iconName].get.ts b/docs/.vitepress/api/icons/[iconName].get.ts
index 435121388..50fa8107a 100644
--- a/docs/.vitepress/api/icons/[iconName].get.ts
+++ b/docs/.vitepress/api/icons/[iconName].get.ts
@@ -38,6 +38,7 @@ export default eventHandler((event) => {
defaultContentType(event, 'image/svg+xml')
setResponseHeader(event, 'Cache-Control', 'public,max-age=31536000')
+ setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return svg
diff --git a/docs/.vitepress/api/tags/index.get.ts b/docs/.vitepress/api/tags/index.get.ts
index 3a30248c5..ab3c94ca5 100644
--- a/docs/.vitepress/api/tags/index.get.ts
+++ b/docs/.vitepress/api/tags/index.get.ts
@@ -3,6 +3,7 @@ import iconMetaData from '../../data/iconMetaData'
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400')
+ setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
return Object.fromEntries(
Object.entries(iconMetaData).map(([name, { tags }]) => [ name, tags ])
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 292fec246..b329dfecd 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -5,6 +5,7 @@ import { createWriteStream } from 'node:fs'
import { resolve } from 'node:path'
import { SitemapStream } from 'sitemap'
import sidebar from './sidebar';
+import fs from 'fs';
const links = []
@@ -35,7 +36,7 @@ export default defineConfig({
)
}
]
- }
+ },
},
head: [
[ 'script', {
@@ -130,10 +131,6 @@ export default defineConfig({
return
}
- if (pageData.relativePath === 'index.md') {
- console.log('Home!');
- }
-
if (pageData.relativePath.startsWith('icons/')) {
links.push({
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
diff --git a/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue b/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
index dc61946d2..df4cb772f 100644
--- a/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
+++ b/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
@@ -1,30 +1,38 @@
@@ -109,9 +117,8 @@
}
.icon-info {
- padding: 0 24px;
+ padding-left: 24px;
flex-basis: 100%;
-
}
.icon-tags {
diff --git a/docs/.vitepress/theme/components/icons/IconInfo.vue b/docs/.vitepress/theme/components/icons/IconInfo.vue
index 9588e2cb3..5cc1612cd 100644
--- a/docs/.vitepress/theme/components/icons/IconInfo.vue
+++ b/docs/.vitepress/theme/components/icons/IconInfo.vue
@@ -17,7 +17,7 @@ const { go } = useRouter()
const { page } = useData()
const tags = computed(() => {
- if (!props.icon) return []
+ if (!props.icon || !props?.icon?.tags) return []
return props.icon.tags.join(' • ')
})
@@ -27,9 +27,11 @@ const tags = computed(() => {
{{ icon.name }}
-
- {{ tags }}
-
+
{
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
- margin-top: 0;;
- margin-bottom: 16px;
line-height: 28px;
+ white-space: nowrap;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.tags-scroller {
+ position: relative;
+ max-width: 100%;
+ width: 100%;
+ height: 28px;
+ padding: 8px 0 16px;
+ margin-bottom: 16px;
+ margin-top: 8px;
+ align-items: center;
+
+ --gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv))
+}
+.horizontal-scroller {
+ overflow-x: scroll;
+ /* Hide Scrollbar */
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
+ scrollbar-color: currentColor transparent; /* foreground background */
+}
+.horizontal-scroller::-webkit-scrollbar {
+ width: 0;
+ display: none
+}
+
+.horizontal-scroller::-webkit-scrollbar-track {
+ background: transparent
+}
+
+.horizontal-scroller::-webkit-scrollbar-thumb {
+ background: transparent;
+ border: none
+}
+
+
+.tags-scroller::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ width: 32px;
+ height: 100%;
+ /* Background Gradient left to right */
+ background: linear-gradient(to right, rgba(255,255,255,0) 0%,var(--gradient-background) 100%);
+ right: 0;
+ pointer-events: none;
}
.buttons {
diff --git a/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue b/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
index ea251c4fd..e178bca6f 100644
--- a/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
+++ b/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
@@ -6,6 +6,9 @@ import InputSearch from '../base/InputSearch.vue'
import useSearchInput from '../../composables/useSearchInput'
import StickyBar from './StickyBar.vue'
import IconsCategory from './IconsCategory.vue'
+import { useFetch } from '@vueuse/core'
+import useFetchTags from '../../composables/useFetchTags'
+import useFetchCategories from '../../composables/useFetchCategories'
const props = defineProps<{
icons: IconEntity[]
@@ -22,7 +25,26 @@ function setActiveIconName(name: string) {
activeIconName.value = name
}
-const searchResults = useSearch(searchQuery, props.icons, [
+const { execute: fetchTags, data: tags } = useFetchTags()
+const { execute: fetchCategories, data: categoriesMap } = useFetchCategories()
+
+const mappedIcons = computed(() => {
+ if(tags.value == null) {
+ return props.icons
+ }
+ return props.icons.map((icon) => {
+ const iconTags = tags.value[icon.name]
+ const iconCategories = categoriesMap.value?.[icon.name] ?? []
+
+ return {
+ ...icon,
+ tags: iconTags,
+ categories: iconCategories,
+ }
+ })
+})
+
+const searchResults = useSearch(searchQuery, mappedIcons, [
{ name: 'name', weight: 2 },
{ name: 'tags', weight: 1 },
])
@@ -37,7 +59,6 @@ const categories = computed(() => {
return iconCategories?.includes(name)
})
-
const searchedCategoryIcons = isSearching
? categoryIcons.filter(icon => searchResults.value.some((item) => item?.name === icon?.name))
: categoryIcons;
@@ -51,9 +72,14 @@ const categories = computed(() => {
.filter(({ icons }) => icons.length)
})
-const activeIcon = computed(() =>
- props.icons?.find((icon) => icon.name === activeIconName.value)
-)
+function onFocusSearchInput() {
+ if (tags.value == null) {
+ fetchTags()
+ }
+ if (categoriesMap.value == null) {
+ fetchCategories()
+ }
+}
const NoResults = defineAsyncComponent(() =>
import('./NoResults.vue')
@@ -71,6 +97,7 @@ const IconDetailOverlay = defineAsyncComponent(() =>
v-model="searchQuery"
class="input-wrapper"
ref="searchInput"
+ @focus="onFocusSearchInput"
/>
/>
diff --git a/docs/.vitepress/theme/components/icons/IconsOverview.vue b/docs/.vitepress/theme/components/icons/IconsOverview.vue
index 446bc8569..88cb48efc 100644
--- a/docs/.vitepress/theme/components/icons/IconsOverview.vue
+++ b/docs/.vitepress/theme/components/icons/IconsOverview.vue
@@ -8,6 +8,8 @@ import useSearch from '../../composables/useSearch'
import EndOfPage from '../base/EndOfPage.vue'
import useSearchInput from '../../composables/useSearchInput'
import StickyBar from './StickyBar.vue'
+import useFetchTags from '../../composables/useFetchTags'
+import useFetchCategories from '../../composables/useFetchCategories'
const props = defineProps<{
icons: IconEntity[]
@@ -22,7 +24,7 @@ const isSmallScreen = useMediaQuery('(min-width: 640px)');
const pageSize = computed(() => {
if(isExtraLargeScreen.value) {
- return 16 * 16;
+ return 16 * 20;
}
if(isLargeScreen.value) {
return 16 * 12;
@@ -38,8 +40,28 @@ const pageSize = computed(() => {
return 10 * 5;
})
+const { execute: fetchTags, data: tags } = useFetchTags()
+const { execute: fetchCategories, data: categories } = useFetchCategories()
+
+const mappedIcons = computed(() => {
+ if(tags.value == null) {
+ return props.icons
+ }
+
+ return props.icons.map((icon) => {
+ const iconTags = tags.value[icon.name]
+ const iconCategories = categories.value?.[icon.name] ?? []
+
+ return {
+ ...icon,
+ tags: iconTags,
+ categories: iconCategories,
+ }
+ })
+})
+
const { searchInput, searchQuery, searchQueryThrottled } = useSearchInput()
-const searchResults = useSearch(searchQueryThrottled, props.icons, [
+const searchResults = useSearch(searchQueryThrottled, mappedIcons, [
{ name: 'name', weight: 3 },
{ name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 },
@@ -58,12 +80,19 @@ function setActiveIconName(name: string) {
activeIconName.value = name
}
-const activeIcon = computed(() => props.icons.find((icon) => icon.name === activeIconName.value))
-
watch(searchQueryThrottled, (searchString) => {
currentPage.value = 1
})
+function onFocusSearchInput() {
+ if (tags.value == null) {
+ fetchTags()
+ }
+ if (categories.value == null) {
+ fetchCategories()
+ }
+}
+
const NoResults = defineAsyncComponent(() =>
import('./NoResults.vue')
)
@@ -81,6 +110,7 @@ const IconDetailOverlay = defineAsyncComponent(() =>
v-model="searchQuery"
ref="searchInput"
class="input-wrapper"
+ @focus="onFocusSearchInput"
/>
diff --git a/docs/.vitepress/theme/components/icons/NoResults.vue b/docs/.vitepress/theme/components/icons/NoResults.vue
index ad6d4fc85..19700e3e3 100644
--- a/docs/.vitepress/theme/components/icons/NoResults.vue
+++ b/docs/.vitepress/theme/components/icons/NoResults.vue
@@ -32,8 +32,8 @@ useEventListener(document, 'mousemove', (mouseEvent) => {
No icons found for '{{ searchQuery }}'
- or
- or
+ {
margin-bottom: 32px;
text-align: center;
}
+
+.text-divider {
+ margin: 12px 0;
+ font-size: 16px;
+ color: var(--vp-c-neutral);
+}
diff --git a/docs/.vitepress/theme/components/icons/SidebarIconCustomizer.vue b/docs/.vitepress/theme/components/icons/SidebarIconCustomizer.vue
index f5194caee..566a1d67e 100644
--- a/docs/.vitepress/theme/components/icons/SidebarIconCustomizer.vue
+++ b/docs/.vitepress/theme/components/icons/SidebarIconCustomizer.vue
@@ -1,7 +1,7 @@
-