Compare commits

...
15 Commits
Author SHA1 Message Date
Eric 1bc57dbee8 Adjust imports 2026-10-19 13:51:57 +02:00
Eric cc69c9f0e5 Updated the search api 2026-10-19 13:23:08 +02:00
Eric 6c17ba30f8 Format code 2026-10-19 13:13:36 +02:00
Eric b78eb72f35 Merge branch 'main' of https://github.com/lucide-icons/lucide into algolia-search 2026-10-19 12:09:29 +02:00
Eric 9c6e275c84 Merge branch 'algolia-docs-search' into algolia-search 2026-10-19 12:07:26 +02:00
Eric c48136c183 Fix search 2026-10-19 12:06:23 +02:00
Eric b2254fa52f Merge branch 'main' of https://github.com/lucide-icons/lucide into algolia-docs-search 2026-10-19 12:02:14 +02:00
Eric 8e324e5c27 Remove search sidebar 2026-10-19 12:01:54 +02:00
Eric Fennis 9d13a32b8a fix(site): Fix build (#5033)
* Fix name

* Fix import
2026-10-19 11:52:32 +02:00
Eric e0efef7cd9 Merge branch 'main' of https://github.com/lucide-icons/lucide into algolia-search 2026-10-19 11:42:16 +02:00
Eric dce1e76788 Fix types 2026-10-15 14:07:07 +02:00
Eric fb2377de17 Add docs search to the sidebar 2026-10-15 13:52:54 +02:00
Eric 88e585400b Adjust formatting 2026-10-15 13:18:35 +02:00
Eric Fennis df8f7d85fc Adjust to lucide 2026-09-21 10:39:53 +02:00
Eric Fennis 420e22b5f1 Adjust search with algolia 2026-09-21 08:54:55 +02:00
18 changed files with 404 additions and 333 deletions
+13
View File
@@ -0,0 +1,13 @@
# Copy to `docs/.env`.
# Icon popularity from Plausible. Defaults to 0 for every icon when unset.
LUCIDE_ANALYTICS_TOKEN=
# Set to `true` to also show icons that are not released yet.
VITE_SHOW_AWAITING_RELEASE=
# Client-side Algolia icon search (public search-only key). Required for the icon search.
# The index is filled by the Algolia crawler.
VITE_ALGOLIA_APP_ID=R7KKMCPXOB
VITE_ALGOLIA_SEARCH_API_KEY=dd3101054267d35fb28f49c42715f1aa
VITE_ALGOLIA_INDEX_NAME=icons
+6 -2
View File
@@ -10,6 +10,7 @@ import { resourcesSidebar } from './sidebar/resources';
import llmstxt from 'vitepress-plugin-llms';
import { transformPageData } from './transformPageData';
import getHeadConfig from './getHeadConfig';
import { loadEnv } from 'vitepress';
const defaultSandpackCSS = await readFile(
fileURLToPath(new URL('./theme/sandpack-default.css', import.meta.url)),
@@ -24,6 +25,9 @@ const title = 'Lucide';
const socialTitle = 'Lucide Icons';
const description = 'Beautiful & consistent icon toolkit made by the community.';
const env = loadEnv(process.env.NODE_ENV, process.cwd());
// https://vitepress.dev/reference/site-config
export default defineConfig({
title,
@@ -134,8 +138,8 @@ export default defineConfig({
search: {
provider: 'algolia',
options: {
appId: 'R7KKMCPXOB',
apiKey: 'dd3101054267d35fb28f49c42715f1aa',
appId: env.VITE_ALGOLIA_APP_ID,
apiKey: env.VITE_ALGOLIA_SEARCH_API_KEY,
indexName: 'lucide',
},
},
+6
View File
@@ -15,6 +15,11 @@ export default async function getStructuredData(iconName: string, pageData: Page
const awaitingRelease = Boolean(params.awaitingRelease);
// Aliases come through either as plain names or as `{ name, deprecated, ... }` objects.
const aliases: string[] = (params.aliases ?? [])
.map((alias: string | { name?: string }) => (typeof alias === 'string' ? alias : alias?.name))
.filter((alias?: string): alias is string => Boolean(alias));
return {
'@context': 'https://schema.org',
'@type': 'WebPage',
@@ -39,6 +44,7 @@ export default async function getStructuredData(iconName: string, pageData: Page
'@type': 'ImageObject',
'@id': `${url}#icon`,
name: iconName,
...(aliases.length ? { alternateName: aliases } : {}),
// TODO: Add support for description extraction from icon metadata
// description: 'An ...SVG icon from the Lucide icon set.',
// `/api/icons/<name>` only renders core icons, a lab name would 404 there.
@@ -1,20 +1,19 @@
<script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent, onMounted } from 'vue';
import type { IconEntity, Category } from '../../types';
import useSearch from '../../composables/useSearch';
import useIconSearch, { entityId } from '../../composables/useIconSearch';
import InputSearch from '../base/InputSearch.vue';
import useSearchInput from '../../composables/useSearchInput';
import useSearchShortcut from '../../utils/useSearchShortcut';
import StickyBar from './StickyBar.vue';
import IconsCategory, { CategoryRow } from './IconsCategory.vue';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import chunkArray from '../../utils/chunkArray';
import useScrollToCategory from '../../composables/useScrollToCategory';
import { useCategoryView } from '../../composables/useCategoryView';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
import { prefetchAlgoliaClient } from '../../utils/algolia';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
@@ -28,7 +27,7 @@ const props = defineProps<{
const activeIconName = ref(null);
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
const { selectedCategory } = useCategoryView();
const isSearching = computed(() => !!searchQuery.value);
const isSearching = computed(() => !!searchQueryDebounced.value);
watch(searchQueryDebounced, (searchString) => {
if (searchString !== '') {
@@ -44,13 +43,6 @@ function setActiveIconName(name: string) {
activeIconName.value = name;
}
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
const {
execute: fetchCategories,
data: categoriesMap,
isFetching: isFetchingCategories,
} = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl);
@@ -58,27 +50,14 @@ const columnSize = computed(() => {
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
});
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] ?? [];
const icons = computed(() => props.icons);
return {
...icon,
tags: iconTags,
categories: iconCategories,
};
});
});
const { results: searchResults, isPending: isSearchPending } = useIconSearch(
searchQueryDebounced,
icons,
);
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 },
{ name: 'aliases', weight: 3 },
{ name: 'tags', weight: 2 },
]);
const resultIds = computed(() => new Set(searchResults.value.map(entityId)));
const categories = computed(() => {
if (!props.categories?.length || !props.icons?.length) return [];
@@ -92,10 +71,8 @@ const categories = computed(() => {
return iconCategories?.includes(name);
});
const searchedCategoryIcons = isSearching
? categoryIcons.filter((icon) =>
searchResults.value.some((item) => item?.name === icon?.name),
)
const searchedCategoryIcons = isSearching.value
? categoryIcons.filter((icon) => resultIds.value.has(entityId(icon)))
: categoryIcons;
return {
@@ -121,8 +98,8 @@ const categoriesList = computed(() => {
}, []);
});
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
const isSearchMetadataLoading = computed(
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
const isSearchSettling = computed(
() => searchQuery.value !== searchQueryDebounced.value || isSearchPending.value,
);
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
@@ -142,21 +119,6 @@ onMounted(() => {
useEventListener(window, 'scroll', containerProps.onScroll);
});
function loadSearchMetadata() {
if (tags.value == null && !isFetchingTags.value) {
void fetchTags();
}
if (categoriesMap.value == null && !isFetchingCategories.value) {
void fetchCategories();
}
}
watch(searchQuery, (searchString) => {
if (searchString !== '') {
loadSearchMetadata();
}
});
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
@@ -191,11 +153,11 @@ function handleCloseDrawer() {
:shortcut="kbdSearchShortcut"
class="input-wrapper"
ref="searchInput"
@focus="loadSearchMetadata"
@focus="prefetchAlgoliaClient"
/>
</StickyBar>
<NoResults
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
v-if="searchPlaceholder.isNoResults && !isSearchSettling"
:searchQuery="searchPlaceholder.query"
:isBrandSearch="searchPlaceholder.isBrand"
@clear="searchQuery = ''"
@@ -2,25 +2,25 @@
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
import type { IconEntity } from '../../types';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import { useRoute } from 'vitepress';
import { DefaultTheme, useData, useRoute } from 'vitepress';
import IconGrid from './IconGrid.vue';
import Select from '../base/Select.vue';
import InputSearch from '../base/InputSearch.vue';
import useSearch from '../../composables/useSearch';
import useIconSearch from '../../composables/useIconSearch';
import useSearchInput from '../../composables/useSearchInput';
import useSearchShortcut from '../../utils/useSearchShortcut';
import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
import chunkArray from '../../utils/chunkArray';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
import { sortIcons, type SortKey } from '../../utils/sortIcons';
import { prefetchAlgoliaClient } from '../../utils/algolia';
import Icon from '@lucide/vue/src/Icon';
import { listSortDescending } from '~/.vitepress/data/iconNodes';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
const SORTING = [
const SORTING: { name: string; value: SortKey }[] = [
{
name: 'Popularity',
value: 'popularity',
@@ -33,7 +33,7 @@ const SORTING = [
name: 'Name',
value: 'name',
},
]
];
const initialGridItems = computed(() => {
if (containerWidth.value === 0) return 120;
@@ -48,15 +48,11 @@ const props = defineProps<{
icons: IconEntity[];
}>();
const activeIconName = ref(null);
const selectedSort = ref(SORTING[0])
const { theme } = useData<DefaultTheme.Config>()
const algoliaSearchOptions = theme.value.search?.options as DefaultTheme.AlgoliaSearchOptions;
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
const {
execute: fetchCategories,
data: categories,
isFetching: isFetchingCategories,
} = useFetchCategories();
const activeIconName = ref(null);
const selectedSort = ref(SORTING[0]);
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl);
@@ -65,45 +61,7 @@ const columnSize = computed(() => {
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
});
const sortedIcons = computed(() => {
switch (selectedSort.value.value) {
case 'popularity':
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
case 'release-date':
return [...props.icons].sort((a, b) => {
if (a.awaitingRelease !== b.awaitingRelease) return a.awaitingRelease ? -1 : 1;
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
return bDate - aDate;
});
case 'name':
return [...props.icons].sort((a, b) => a.name.localeCompare(b.name));
default:
return props.icons;
}
});
const mappedIcons = computed(() => {
if (tags.value == null) {
return sortedIcons.value;
}
if (categories.value == null) {
return sortedIcons.value;
}
return sortedIcons.value.map((icon) => {
const iconTags = tags.value[icon.name];
const iconCategories = categories.value[icon.name] ?? [];
return {
...icon,
tags: iconTags,
categories: iconCategories,
};
});
});
const sortedIcons = computed(() => sortIcons(props.icons, selectedSort.value.value));
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
@@ -111,20 +69,29 @@ const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
searchInput.value?.focus();
});
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
{ name: 'name', weight: 3 },
{ name: 'aliases', weight: 8 },
{ name: 'tags', weight: 2 },
{ name: 'categories', weight: 1 },
]);
const { results: searchResults, isPending: isSearchPending } = useIconSearch(
searchQueryDebounced,
sortedIcons,
algoliaSearchOptions,
);
// While searching, "Popularity" keeps Algolia's relevance order.
// The other sort options re-sort the matching icons locally.
const displayedIcons = computed(() => {
if (searchQueryDebounced.value && selectedSort.value.value !== 'popularity') {
return sortIcons(searchResults.value, selectedSort.value.value);
}
return searchResults.value;
});
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
const isSearchMetadataLoading = computed(
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
const isSearchSettling = computed(
() => searchQuery.value !== searchQueryDebounced.value || isSearchPending.value,
);
const chunkedIcons = computed(() => {
return chunkArray(searchResults.value, columnSize.value);
return chunkArray(displayedIcons.value, columnSize.value);
});
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(chunkedIcons, {
@@ -147,21 +114,6 @@ function setActiveIconName(name: string) {
activeIconName.value = name;
}
function loadSearchMetadata() {
if (tags.value == null && !isFetchingTags.value) {
void fetchTags();
}
if (categories.value == null && !isFetchingCategories.value) {
void fetchCategories();
}
}
watch(searchQuery, (searchString) => {
if (searchString !== '') {
loadSearchMetadata();
}
});
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
@@ -197,7 +149,7 @@ function handleCloseDrawer() {
ref="searchInput"
:shortcut="kbdSearchShortcut"
class="input-wrapper"
@focus="loadSearchMetadata"
@focus="() =>prefetchAlgoliaClient(algoliaSearchOptions)"
/>
<Select
@@ -216,7 +168,7 @@ function handleCloseDrawer() {
</Select>
</StickyBar>
<NoResults
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
v-if="searchPlaceholder.isNoResults && !isSearchSettling"
:searchQuery="searchPlaceholder.query"
:isBrandSearch="searchPlaceholder.isBrand"
@clear="searchQuery = ''"
@@ -224,7 +176,7 @@ function handleCloseDrawer() {
<IconGrid
v-else-if="list.length === 0"
overlayMode
:icons="searchResults.slice(0, initialGridItems)"
:icons="displayedIcons.slice(0, initialGridItems)"
:activeIcon="activeIconName"
@setActiveIcon="setActiveIconName"
/>
@@ -22,13 +22,15 @@ import FakeInput from '../base/FakeInput.vue'
const VPAlgoliaSearchBox = defineAsyncComponent(() => import('vitepress/dist/client/theme-default/components/VPAlgoliaSearchBox.vue'));
const { theme } = useData<DefaultTheme.Config>()
const { theme, page } = useData<DefaultTheme.Config>()
const algolia = computed(
() =>
(theme.value.search?.options ?? theme.value.algolia) as DefaultTheme.AlgoliaSearchOptions,
)
const isGuide = computed(() => page.value?.relativePath?.startsWith?.('guide'));
// whether this instance is the one rendering the (hidden) search box
const isOwner = ref(false)
const shortcutText = ref('')
@@ -117,7 +119,7 @@ function isEditingContent(event: KeyboardEvent): boolean {
</script>
<template>
<div class="VPNavBarSearch">
<div class="VPNavBarSearch" v-if="isGuide">
<FakeInput
class="nav-search-input"
:shortcut="shortcutText"
@@ -136,27 +138,29 @@ function isEditingContent(event: KeyboardEvent): boolean {
<style>
.VPNavBarSearch {
display: flex;
align-items: center;
}
@media (min-width: 768px) {
.VPNavBarSearch {
flex-grow: 1;
padding-left: 0px;
padding-left: 24px;
}
}
@media (min-width: 960px) {
.VPNavBarSearch {
padding-left: 0px;
padding-left: 32px;
}
}
.VPNavBarSearch {
padding-top: 24px;
}
.VPNavBarSearch .nav-search-input {
padding: 8px 12px;
align-items: center;
width: 100%;
max-width: 240px;
}
.VPNavBarSearch .docsearch-host {
@@ -1,12 +0,0 @@
import { useFetch } from '@vueuse/core';
const useFetchCategories = () =>
useFetch<Record<string, string[]>>(
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/categories`,
{
immediate:
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
},
).json();
export default useFetchCategories;
@@ -1,12 +0,0 @@
import { useFetch } from '@vueuse/core';
const useFetchTags = () =>
useFetch<Record<string, string[]>>(
`${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/tags`,
{
immediate:
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('search'),
},
).json();
export default useFetchTags;
@@ -0,0 +1,83 @@
import { computed, ref, shallowRef, watch, type ComputedRef, type Ref } from 'vue';
import { searchIconIds } from '../utils/algolia';
import { DefaultTheme, useData } from 'vitepress';
export interface SearchableIcon {
name: string;
externalLibrary?: string;
}
/**
* Matches the `objectID` scheme of the Algolia `icons` index:
* `name` for core icons, `lab/<name>` for lab icons.
*/
export const entityId = (icon: SearchableIcon) =>
icon.externalLibrary ? `${icon.externalLibrary}/${icon.name}` : icon.name;
export interface UseIconSearchResult<T> {
/** Matching icons in relevance order, or the full collection when the query is empty. */
results: ComputedRef<T[]>;
/** `true` while a search request is in flight. */
isPending: Ref<boolean>;
}
const useIconSearch = <T extends SearchableIcon>(
query: Ref<string>,
collection: Ref<T[]>,
algoliaSearchOptions: DefaultTheme.AlgoliaSearchOptions,
): UseIconSearchResult<T> => {
const byId = computed(() => new Map(collection.value.map((icon) => [entityId(icon), icon])));
const hitIds = shallowRef<string[]>([]);
const isPending = ref(false);
let requestSeq = 0;
watch(
query,
async (searchQuery) => {
const seq = ++requestSeq;
if (!searchQuery) {
isPending.value = false;
return;
}
isPending.value = true;
try {
const ids = await searchIconIds(searchQuery, algoliaSearchOptions);
if (seq === requestSeq) {
hitIds.value = ids;
}
} catch (error) {
console.error('Icon search failed', error);
if (seq === requestSeq) {
hitIds.value = [];
}
} finally {
if (seq === requestSeq) {
isPending.value = false;
}
}
},
{ immediate: true },
);
const results = computed(() => {
if (!query.value) {
return collection.value;
}
// Hits without a local entity are dropped: lab icons while the Lab library is not
// loaded, or icons the crawler indexed after this build was deployed.
return hitIds.value
.map((id) => byId.value.get(id))
.filter((icon): icon is T => icon !== undefined);
});
return { results, isPending };
};
export default useIconSearch;
@@ -1,32 +0,0 @@
import Fuse, { type FuseOptionKeyObject } from 'fuse.js';
import { shallowRef, computed, Ref } from 'vue';
const useSearch = <T>(
query: Ref<string>,
collection: Ref<T[]>,
keys: FuseOptionKeyObject<T>[] = [],
) => {
const index = shallowRef(
new Fuse(collection.value, {
threshold: 0.2,
useExtendedSearch: true,
keys,
}),
);
const results = computed(() => {
index.value.setCollection(collection.value);
if (query.value) {
return index.value
.search({ $and: query.value.split(' ').filter((t) => !!t) })
.map((result) => result.item);
}
return collection.value;
});
return results;
};
export default useSearch;
@@ -2,7 +2,6 @@
import { computed } from 'vue';
import { useData } from 'vitepress';
import VPSidebarGroup from 'vitepress/dist/client/theme-default/components/VPSidebarGroup.vue';
import VPNavBarSearch from '../components/overrides/VPNavBarSearch.vue';
import FrameworkSelect from '../components/guide/FrameworkSelect.vue';
import { guideSidebarTop } from '../../sidebar';
@@ -13,7 +12,6 @@ const isGuide = computed(() => page.value?.relativePath?.startsWith?.('guide'));
<template>
<template v-if="isGuide">
<VPNavBarSearch />
<VPSidebarGroup :items="guideSidebarTop" />
<FrameworkSelect />
</template>
-4
View File
@@ -129,10 +129,6 @@ html {
flex-direction: column-reverse;
}
.content-body .search {
display: none;
}
@media (min-width: 960px) {
.VPHomeHero .container .image {
order: 1;
+70
View File
@@ -0,0 +1,70 @@
import type { LiteClient } from 'algoliasearch/lite';
import { DefaultTheme, useData } from 'vitepress';
/**
* The index caps retrievable hits at 1000 per query (`paginationLimitedTo`),
* so a single page of this size is the most we can ever get back.
*/
const HITS_PER_PAGE = 1000;
let clientPromise: Promise<LiteClient> | undefined;
/**
* Lazily loads the Algolia lite client so it stays out of the initial page bundle.
* Call it on search input focus to prefetch before the first keystroke.
*/
export function getAlgoliaClient(algoliaSearchOptions: DefaultTheme.AlgoliaSearchOptions): Promise<LiteClient> {
clientPromise ??= import('algoliasearch/lite')
.then(({ liteClient }) =>
liteClient(algoliaSearchOptions.appId, algoliaSearchOptions.apiKey),
)
.catch((error) => {
// Allow a retry on the next call instead of caching the failure.
clientPromise = undefined;
throw error;
});
return clientPromise;
}
export function prefetchAlgoliaClient(algoliaSearchOptions: DefaultTheme.AlgoliaSearchOptions) {
getAlgoliaClient(algoliaSearchOptions).catch(() => {
// Ignored here, the actual search reports errors.
});
}
const cache = new Map<string, string[]>();
/**
* Searches the icon index and returns the matching `objectID`s in relevance order.
* Core icons use their name as `objectID`, lab icons use `lab/<name>`.
*/
export async function searchIconIds(query: string, algoliaSearchOptions: DefaultTheme.AlgoliaSearchOptions): Promise<string[]> {
const cached = cache.get(query);
if (cached) {
return cached;
}
const client = await getAlgoliaClient(algoliaSearchOptions);
const {
results: [result],
} = await client.searchForHits<{ objectID: string }>({
requests: [
{
indexName: 'icons',
query,
hitsPerPage: HITS_PER_PAGE,
attributesToRetrieve: ['objectID'],
attributesToHighlight: [],
},
],
});
const ids = result.hits.map((hit) => hit.objectID);
cache.set(query, ids);
return ids;
}
+24
View File
@@ -0,0 +1,24 @@
import type { IconEntity } from '../types';
export type SortKey = 'popularity' | 'release-date' | 'name';
type SortableIcon = Pick<IconEntity, 'name' | 'popularity' | 'createdRelease' | 'awaitingRelease'>;
export function sortIcons<T extends SortableIcon>(icons: T[], sort: SortKey): T[] {
switch (sort) {
case 'popularity':
return [...icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
case 'release-date':
return [...icons].sort((a, b) => {
if (a.awaitingRelease !== b.awaitingRelease) return a.awaitingRelease ? -1 : 1;
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
return bDate - aDate;
});
case 'name':
return [...icons].sort((a, b) => a.name.localeCompare(b.name));
default:
return icons;
}
}
+7
View File
@@ -35,6 +35,13 @@ declare global {
// Defined by VitePress at build time, used by its VPDocAsideCarbonAds component
const __CARBON__: boolean;
// Algolia icon search, see docs/.env.example
interface ImportMetaEnv {
readonly VITE_ALGOLIA_APP_ID: string;
readonly VITE_ALGOLIA_SEARCH_API_KEY: string;
readonly VITE_ALGOLIA_INDEX_NAME: string;
}
interface Window {
_carbonads?: unknown;
ExpoSnack?: {
+8
View File
@@ -18,6 +18,14 @@ pnpm docs:dev
pnpm dev
```
## Environment variables
Copy `.env.example` to `.env` if you need any of them. All variables are optional; see the comments in `.env.example`.
## Icon search
Search on `/icons` and `/icons/categories` queries the Algolia `icons` index, which is filled by the Algolia crawler. The site only reads from it (`.vitepress/theme/utils/algolia.ts`), it never writes to it.
## Build
```sh
+1 -1
View File
@@ -65,8 +65,8 @@
"@vueuse/components": "^14.4.0",
"@vueuse/core": "^14.4.0",
"@vueuse/integrations": "^14.4.0",
"algoliasearch": "^5.59.0",
"element-to-path": "^1.2.1",
"fuse.js": "^7.5.0",
"jszip": "^3.10.1",
"lodash-es": "^4.18.1",
"lucide": "workspace:*",
+127 -127
View File
@@ -155,12 +155,12 @@ importers:
'@vueuse/integrations':
specifier: ^14.4.0
version: 14.4.0(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.44(typescript@6.0.3))
algoliasearch:
specifier: ^5.59.0
version: 5.59.0
element-to-path:
specifier: ^1.2.1
version: 1.2.1
fuse.js:
specifier: ^7.5.0
version: 7.5.0
jszip:
specifier: ^3.10.1
version: 3.10.1
@@ -191,7 +191,7 @@ importers:
devDependencies:
'@docsearch/js':
specifier: 3.8.2
version: 3.8.2(@algolia/client-search@5.57.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)
version: 3.8.2(@algolia/client-search@5.59.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)
'@lucide/build-icons':
specifier: workspace:*
version: link:../tools/build-icons
@@ -257,7 +257,7 @@ importers:
version: 6.0.3
vitepress:
specifier: ^1.6.4
version: 1.6.4(@algolia/client-search@5.57.0)(@types/node@26.4.1)(@types/react@19.3.0)(esbuild@0.28.2)(fuse.js@7.5.0)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(postcss@8.5.30)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(sass-embedded@1.105.1)(sass@1.105.1)(search-insights@2.8.2)(stylus@0.56.0(supports-color@10.2.2))(terser@5.51.2)(typescript@6.0.3)(yaml@2.9.0)
version: 1.6.4(@algolia/client-search@5.59.0)(@types/node@26.4.1)(@types/react@19.3.0)(esbuild@0.28.2)(fuse.js@7.5.0)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(postcss@8.5.30)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(sass-embedded@1.105.1)(sass@1.105.1)(search-insights@2.8.2)(stylus@0.56.0(supports-color@10.2.2))(terser@5.51.2)(typescript@6.0.3)(yaml@2.9.0)
vitepress-plugin-group-icons:
specifier: ^1.7.6
version: 1.7.6(vite@8.3.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(sass-embedded@1.105.1)(sass@1.105.1)(stylus@0.56.0(supports-color@10.2.2))(terser@5.51.2)(yaml@2.9.0))
@@ -1514,8 +1514,8 @@ packages:
'@adobe/css-tools@4.5.0':
resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==}
'@algolia/abtesting@1.23.0':
resolution: {integrity: sha512-j45MBISstltys9QyQ4xf6quRiN1g7vMuwQL9VM4dx8YuRZvCQ173b9royZAx6iAbRX3IB1VnG1z//NuwyQ8jpQ==}
'@algolia/abtesting@1.25.0':
resolution: {integrity: sha512-rSTin9Uta23uaewYVQEp8XI9T3iA/zrg0/1G2vhf8oFFDxFL5vybnZ5IQwsVAg4JpKxPX4/WYNKdcfWrZymk7w==}
engines: {node: '>= 14.0.0'}
'@algolia/autocomplete-core@1.17.7':
@@ -1538,56 +1538,56 @@ packages:
'@algolia/client-search': '>= 4.9.1 < 6'
algoliasearch: '>= 4.9.1 < 6'
'@algolia/client-abtesting@5.57.0':
resolution: {integrity: sha512-JVFFujiZUCguk5tz3LZr4fTQxqpIrj4/Jw3SI7kMljSqtfLxYn/s/TWH0J2s4iNfsDpxPhgFGMotCpmDI4kZ8w==}
'@algolia/client-abtesting@5.59.0':
resolution: {integrity: sha512-bm2XN0hCSMYwStSsCBT0/PUB2BDxoyR1Lnub3c392HMEy9bi8PUSW8vR6zltVEKWG2t4PQFWMD5C07bmJxGgPg==}
engines: {node: '>= 14.0.0'}
'@algolia/client-analytics@5.57.0':
resolution: {integrity: sha512-6KqECK4ED3JJQEoDrQWnGPQzElA828xAD4qK5ceawNNyP/LcSvzAoLHjFkoTPksZ/kxj6VUtCRH+IHZesLltng==}
'@algolia/client-analytics@5.59.0':
resolution: {integrity: sha512-XOFPOTa69WuqHR6c5tMgnUUwwqQgNSzMpxmhrgA9KmxRf8WIqEa0cokHJvohk5CYb7CZx0xeSL6Bk2IUJ7Lv7Q==}
engines: {node: '>= 14.0.0'}
'@algolia/client-common@5.57.0':
resolution: {integrity: sha512-uqpGF3oXYsoCbQq5d7BzNrNTfIfuvJyGP1CKvSW27T9boUg7KOwyxsAw1AX0a3jSW2HrYEJ/NN+Z4MiGivbpeQ==}
'@algolia/client-common@5.59.0':
resolution: {integrity: sha512-PC8ipLOYFKRTfIUY1J3FJxS6ryzWziaXmIX9/sNMoUR8L+XhF7hX2QAeUI87YVl5zujvlYTSOb+HAIqKXDjyHQ==}
engines: {node: '>= 14.0.0'}
'@algolia/client-insights@5.57.0':
resolution: {integrity: sha512-u5NboJVJXDEFplvNnqqX4CxkXPYysjJRj47hOSh9329H8kG5gFLKJBIiS5utMQ+GZm8xQl3Te7NInDk6elEADQ==}
'@algolia/client-insights@5.59.0':
resolution: {integrity: sha512-yFNcCMM5fHiyoR0HuxMrzy+VjDcmhFUZTm2IJ2DHwGsVH3B5SEob4zTmeEZ3j/AZqNnmrJOCKl/tJnBg7+84bA==}
engines: {node: '>= 14.0.0'}
'@algolia/client-personalization@5.57.0':
resolution: {integrity: sha512-uzc0b2LmHAK9/QID4xeo35OG84AkZl4YewkCqawqAOGLjT2eZpM/OZx45ESygMHG30Ws+ZTSdluPtMJcUnrbWQ==}
'@algolia/client-personalization@5.59.0':
resolution: {integrity: sha512-GYja6HkDt2VrQhWmB2cLx3Z5fDwI9no7q+xwCWcFrTPm5CLH9QZvK0XLO9co1FcNIDxxkMaP3AXM39/XcecEOg==}
engines: {node: '>= 14.0.0'}
'@algolia/client-query-suggestions@5.57.0':
resolution: {integrity: sha512-dIAhnM6ue/ssa5PjgNfu4g8A4yTojl9ZOUzZU3wIaIKRerL2R/3Emuf9n/D6ICXXP167KC6XCeC7nliSw7cuSw==}
'@algolia/client-query-suggestions@5.59.0':
resolution: {integrity: sha512-Wofg7bMpWh8N5qDDZs0wy6whc+KMmdNsxgrIGp9Ug2s1Bka0uq7AjyckdxReKPHLA0Q1qH8X/SNh0wn2t2X2Lw==}
engines: {node: '>= 14.0.0'}
'@algolia/client-search@5.57.0':
resolution: {integrity: sha512-2TTPTTKSJmCptvhCm4Xf3bBYMqZni+Pgc2hVdqc4l9wsBpSJNVTVIKpnd10OubUgkGcmppVDj1XQqYaf6EnPSQ==}
'@algolia/client-search@5.59.0':
resolution: {integrity: sha512-fHnALZfbEnODczGk14Y/1YBRApp6UEpZUTexGcMUPzY7RDc7q4HN2Y6jh0KUG8Jo9B+q+wOoQEc3ziR0ErbuFg==}
engines: {node: '>= 14.0.0'}
'@algolia/ingestion@1.57.0':
resolution: {integrity: sha512-W4JseHKt+pzOxlFV+T3MWEG0h4Z2Se5zjoXUD0ewlw8aOWMG/yjRdopUdLQsXULepB/My2tDuZjkwk2sMfsUrQ==}
'@algolia/ingestion@1.59.0':
resolution: {integrity: sha512-Fa38s1mHgoaLCT117sfJ6P78rtxUt93CYxBYpq1VOIcslaF+cH+1h5uikAPsKfxLnsuEDZHiBiaI6eNPTsQMRA==}
engines: {node: '>= 14.0.0'}
'@algolia/monitoring@1.57.0':
resolution: {integrity: sha512-BrxJVE0/eLinEPICCD7BKN/2xnt0nkjge70u8zzE2ISP3fuB3tjLgcwpanUycvlHBFLI4gK0l5ol54p6IYuR/Q==}
'@algolia/monitoring@1.59.0':
resolution: {integrity: sha512-NyNsRSqM2tF1MX7ZGw/j4rduoEJiQ5wfvbSo/CFVdEzYCjyxbGFRPOZyS/GETJG9rk1TdHOq8NZqKqk/u1fm4g==}
engines: {node: '>= 14.0.0'}
'@algolia/recommend@5.57.0':
resolution: {integrity: sha512-Gc29jkeiLKlVfHvyrIgyUHHE+aYTdXEeLfK42rjr5/1TTVsYwUJz0XkvoIBIqfMjcDg6gXeHb1jTUZ0H+SYIlQ==}
'@algolia/recommend@5.59.0':
resolution: {integrity: sha512-nXBK2uygWtvbCOffMWqqfjjcEtp9enDKY5/2Pw/Hhw8VVaOyK2ThbGK04bNX/Le+qoK1VHYkodWGVTKfw0Otkw==}
engines: {node: '>= 14.0.0'}
'@algolia/requester-browser-xhr@5.57.0':
resolution: {integrity: sha512-PIPnPN7MP3fp2VAi01BVXhCWmD366ZB2Hkq5TlYKtThd4KxUtMmaaNDpFgVCTXtSIqWVZLJntOHRvxg/sIPd8Q==}
'@algolia/requester-browser-xhr@5.59.0':
resolution: {integrity: sha512-yb+4afX/zja8QwX0KmV4/ae2kyYkRknsE79CRusYS2U5D8qwn2wq0cn1x12f3tm3F3+5FgmDdbuGdQTuCLSKMQ==}
engines: {node: '>= 14.0.0'}
'@algolia/requester-fetch@5.57.0':
resolution: {integrity: sha512-AX3RlOudXMdTwtwUqdAf5hAVLvXfOZZH1FZh6ALDdrhVLT0TtAIe48N6nYcWcTnwoTxK/wDIQqZ19IMjK8zJAA==}
'@algolia/requester-fetch@5.59.0':
resolution: {integrity: sha512-Lp52TmpA1QtNmdHzs505Xwf4tgII7RAapkDSF9AAtWIBETcGaTIaXci4Rd9nS6SdaWK1BWTSqAPp5wzh6UN3Ag==}
engines: {node: '>= 14.0.0'}
'@algolia/requester-node-http@5.57.0':
resolution: {integrity: sha512-cWZc1dKb7wy9/wPpwMtL1y89gK2G7y2A47Coa7zwf1ydtIeJm4+S+XxoQ2b/ZRiQnrC1YHavLjYUPDCdnT7Khg==}
'@algolia/requester-node-http@5.59.0':
resolution: {integrity: sha512-YYHLEs5rC6oRTFwT7bJaKBR9NSFzikeVHGAT1ffATmUQzR8XqyhHXjoieAUoR7fBU6I5cfy1FVeh5GoRgriuzA==}
engines: {node: '>= 14.0.0'}
'@ampproject/remapping@2.3.0':
@@ -7179,8 +7179,8 @@ packages:
ajv@8.20.0:
resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
algoliasearch@5.57.0:
resolution: {integrity: sha512-HpND7MBGctOAkd1GoQoDZCGoCpqNTS5NG1LuhElFet3RdLJkwnyTYZXZhXwtpAQPrI36fqQ3eT6KQrdKDTKu3A==}
algoliasearch@5.59.0:
resolution: {integrity: sha512-wUXzaeI7B526W4y1gFg3lcxgDZ67XSgRJIiellYWOas/pLpO7rOtxm9Gr2E/C8aiSDXIgx1q5TdSsvK67Uakqw==}
engines: {node: '>= 14.0.0'}
alien-signals@3.2.1:
@@ -13853,117 +13853,117 @@ snapshots:
'@adobe/css-tools@4.5.0': {}
'@algolia/abtesting@1.23.0':
'@algolia/abtesting@1.25.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.8.2)':
'@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)(search-insights@2.8.2)':
dependencies:
'@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.8.2)
'@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
'@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)(search-insights@2.8.2)
'@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)
transitivePeerDependencies:
- '@algolia/client-search'
- algoliasearch
- search-insights
'@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.8.2)':
'@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)(search-insights@2.8.2)':
dependencies:
'@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
'@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)
search-insights: 2.8.2
transitivePeerDependencies:
- '@algolia/client-search'
- algoliasearch
'@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)':
'@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)':
dependencies:
'@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
'@algolia/client-search': 5.57.0
algoliasearch: 5.57.0
'@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)
'@algolia/client-search': 5.59.0
algoliasearch: 5.59.0
'@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)':
'@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)':
dependencies:
'@algolia/client-search': 5.57.0
algoliasearch: 5.57.0
'@algolia/client-search': 5.59.0
algoliasearch: 5.59.0
'@algolia/client-abtesting@5.57.0':
'@algolia/client-abtesting@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/client-analytics@5.57.0':
'@algolia/client-analytics@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/client-common@5.57.0': {}
'@algolia/client-common@5.59.0': {}
'@algolia/client-insights@5.57.0':
'@algolia/client-insights@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/client-personalization@5.57.0':
'@algolia/client-personalization@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/client-query-suggestions@5.57.0':
'@algolia/client-query-suggestions@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/client-search@5.57.0':
'@algolia/client-search@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/ingestion@1.57.0':
'@algolia/ingestion@1.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/monitoring@1.57.0':
'@algolia/monitoring@1.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/recommend@5.57.0':
'@algolia/recommend@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
'@algolia/requester-browser-xhr@5.57.0':
'@algolia/requester-browser-xhr@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-fetch@5.57.0':
'@algolia/requester-fetch@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/client-common': 5.59.0
'@algolia/requester-node-http@5.57.0':
'@algolia/requester-node-http@5.59.0':
dependencies:
'@algolia/client-common': 5.57.0
'@algolia/client-common': 5.59.0
'@ampproject/remapping@2.3.0':
dependencies:
@@ -15630,9 +15630,9 @@ snapshots:
'@docsearch/css@3.8.2': {}
'@docsearch/js@3.8.2(@algolia/client-search@5.57.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)':
'@docsearch/js@3.8.2(@algolia/client-search@5.59.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)':
dependencies:
'@docsearch/react': 3.8.2(@algolia/client-search@5.57.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)
'@docsearch/react': 3.8.2(@algolia/client-search@5.59.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)
preact: 10.29.8
transitivePeerDependencies:
- '@algolia/client-search'
@@ -15642,12 +15642,12 @@ snapshots:
- react-dom
- search-insights
'@docsearch/react@3.8.2(@algolia/client-search@5.57.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)':
'@docsearch/react@3.8.2(@algolia/client-search@5.59.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)':
dependencies:
'@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.8.2)
'@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
'@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)(search-insights@2.8.2)
'@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.59.0)(algoliasearch@5.59.0)
'@docsearch/css': 3.8.2
algoliasearch: 5.57.0
algoliasearch: 5.59.0
optionalDependencies:
'@types/react': 19.3.0
react: 19.3.1
@@ -19833,22 +19833,22 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
algoliasearch@5.57.0:
algoliasearch@5.59.0:
dependencies:
'@algolia/abtesting': 1.23.0
'@algolia/client-abtesting': 5.57.0
'@algolia/client-analytics': 5.57.0
'@algolia/client-common': 5.57.0
'@algolia/client-insights': 5.57.0
'@algolia/client-personalization': 5.57.0
'@algolia/client-query-suggestions': 5.57.0
'@algolia/client-search': 5.57.0
'@algolia/ingestion': 1.57.0
'@algolia/monitoring': 1.57.0
'@algolia/recommend': 5.57.0
'@algolia/requester-browser-xhr': 5.57.0
'@algolia/requester-fetch': 5.57.0
'@algolia/requester-node-http': 5.57.0
'@algolia/abtesting': 1.25.0
'@algolia/client-abtesting': 5.59.0
'@algolia/client-analytics': 5.59.0
'@algolia/client-common': 5.59.0
'@algolia/client-insights': 5.59.0
'@algolia/client-personalization': 5.59.0
'@algolia/client-query-suggestions': 5.59.0
'@algolia/client-search': 5.59.0
'@algolia/ingestion': 1.59.0
'@algolia/monitoring': 1.59.0
'@algolia/recommend': 5.59.0
'@algolia/requester-browser-xhr': 5.59.0
'@algolia/requester-fetch': 5.59.0
'@algolia/requester-node-http': 5.59.0
alien-signals@3.2.1: {}
@@ -27162,10 +27162,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
vitepress@1.6.4(@algolia/client-search@5.57.0)(@types/node@26.4.1)(@types/react@19.3.0)(esbuild@0.28.2)(fuse.js@7.5.0)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(postcss@8.5.30)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(sass-embedded@1.105.1)(sass@1.105.1)(search-insights@2.8.2)(stylus@0.56.0(supports-color@10.2.2))(terser@5.51.2)(typescript@6.0.3)(yaml@2.9.0):
vitepress@1.6.4(@algolia/client-search@5.59.0)(@types/node@26.4.1)(@types/react@19.3.0)(esbuild@0.28.2)(fuse.js@7.5.0)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(postcss@8.5.30)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(sass-embedded@1.105.1)(sass@1.105.1)(search-insights@2.8.2)(stylus@0.56.0(supports-color@10.2.2))(terser@5.51.2)(typescript@6.0.3)(yaml@2.9.0):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.57.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)
'@docsearch/js': 3.8.2(@algolia/client-search@5.59.0)(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(search-insights@2.8.2)
'@iconify-json/simple-icons': 1.2.94
'@shikijs/core': 2.5.0
'@shikijs/transformers': 2.5.0