diff --git a/docs/.vitepress/getStructuredData.ts b/docs/.vitepress/getStructuredData.ts index 2ac47df1e..f66dcee29 100644 --- a/docs/.vitepress/getStructuredData.ts +++ b/docs/.vitepress/getStructuredData.ts @@ -1,7 +1,7 @@ import { PageData } from 'vitepress'; export default async function getStructuredData(iconName: string, pageData: PageData) { - const url = `https://lucide.dev/icons/${iconName}`; + const url = `https://lucide.dev/icons/${iconName}/details`; return { '@context': 'https://schema.org', diff --git a/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue b/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue index 2b1bca928..6529de488 100644 --- a/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue +++ b/docs/.vitepress/theme/components/icons/IconDetailOverlay.vue @@ -87,8 +87,8 @@ const Expand = createLucideIcon('Expand', expand); @click=" go( icon.externalLibrary - ? `/icons/${icon.externalLibrary}/${icon.name}` - : `/icons/${icon.name}`, + ? `/icons/${icon.externalLibrary}/${icon.name}/details` + : `/icons/${icon.name}/details`, ) " > diff --git a/docs/.vitepress/theme/components/icons/IconInfo.vue b/docs/.vitepress/theme/components/icons/IconInfo.vue index 1aa8cb76d..f79c6fcb5 100644 --- a/docs/.vitepress/theme/components/icons/IconInfo.vue +++ b/docs/.vitepress/theme/components/icons/IconInfo.vue @@ -33,12 +33,21 @@ const deprecatedTitle = computed(() => { }); }); +const isDetailsPage = computed(() => { + const relativePath = page.value?.relativePath ?? ''; + const name = props.icon.name; + if (props.icon.externalLibrary) { + return relativePath === `icons/${props.icon.externalLibrary}/${name}/details.md`; + } + return relativePath === `icons/${name}/details.md`; +}); + const iconPath = computed(() => { if (props.icon.externalLibrary) { - return `icons/${props.icon.externalLibrary}/${props.icon.name}`; + return `/icons/${props.icon.externalLibrary}/${props.icon.name}/details`; } - return `icons/${props.icon.name}`; + return `/icons/${props.icon.name}/details`; }); @@ -79,7 +88,7 @@ const iconPath = computed(() => {
{ return createLucideIcon(props.name, props.iconNode); }); -const href = computed(() => - props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`, -); +const href = computed(() => { + if (props.overlayMode) { + return props.externalLibrary + ? `/icons/${props.externalLibrary}/${props.name}` + : `/icons/${props.name}`; + } + + return props.externalLibrary + ? `/icons/${props.externalLibrary}/${props.name}/details` + : `/icons/${props.name}/details`; +}); async function navigateToIcon(event) { if (event.shiftKey) { @@ -55,24 +63,14 @@ async function navigateToIcon(event) { if (props.overlayMode && showOverlay.value) { event.preventDefault(); - window.history.pushState( - {}, - '', - props.externalLibrary - ? `/icons/${props.externalLibrary}/${props.name}` - : `/icons/${props.name}`, - ); + window.history.pushState({}, '', href.value); emit( 'setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}` : props.name, ); } else { event.preventDefault(); - go( - props.externalLibrary - ? `/icons/${props.externalLibrary}/${props.name}` - : `/icons/${props.name}`, - ); + go(href.value); } } diff --git a/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue b/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue index e3aab9189..1031be66e 100644 --- a/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue +++ b/docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue @@ -137,9 +137,28 @@ useScrollToCategory({ searchQueryDebounced, }); +function onPopState() { + if (typeof window === 'undefined') return; + const pathname = window.location.pathname; + const match = pathname.match(/^\/icons\/(?:(lab)\/)?([^/]+)\/?$/); + if (match) { + const [, lib, name] = match; + if (name && name !== 'categories' && name !== 'lab') { + activeIconName.value = lib ? `${lib}:${name}` : name; + return; + } + } + activeIconName.value = null; +} + onMounted(() => { containerProps.ref.value = document.documentElement; useEventListener(window, 'scroll', containerProps.onScroll); + useEventListener(window, 'popstate', onPopState); + + if (!activeIconName.value && typeof window !== 'undefined') { + onPopState(); + } }); function loadSearchMetadata() { diff --git a/docs/.vitepress/theme/components/icons/IconsOverview.vue b/docs/.vitepress/theme/components/icons/IconsOverview.vue index a5dde2d5b..144af931e 100644 --- a/docs/.vitepress/theme/components/icons/IconsOverview.vue +++ b/docs/.vitepress/theme/components/icons/IconsOverview.vue @@ -46,11 +46,21 @@ const initialGridItems = computed(() => { const props = defineProps<{ icons: IconEntity[]; + activeIcon?: string | null; }>(); -const activeIconName = ref(null); +const activeIconName = ref(props.activeIcon ?? null); const selectedSort = ref(SORTING[0]) +watch( + () => props.activeIcon, + (newVal) => { + if (newVal !== undefined) { + activeIconName.value = newVal; + } + }, +); + const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags(); const { execute: fetchCategories, @@ -132,9 +142,28 @@ const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(chunkedI overscan: 10, }); +function onPopState() { + if (typeof window === 'undefined') return; + const pathname = window.location.pathname; + const match = pathname.match(/^\/icons\/(?:(lab)\/)?([^/]+)\/?$/); + if (match) { + const [, lib, name] = match; + if (name && name !== 'categories' && name !== 'lab') { + activeIconName.value = lib ? `${lib}:${name}` : name; + return; + } + } + activeIconName.value = null; +} + onMounted(() => { containerProps.ref.value = document.documentElement; useEventListener(window, 'scroll', containerProps.onScroll); + useEventListener(window, 'popstate', onPopState); + + if (!activeIconName.value && typeof window !== 'undefined') { + onPopState(); + } // Check if we should focus the search input from URL parameter const route = useRoute(); diff --git a/docs/.vitepress/transformPageData.ts b/docs/.vitepress/transformPageData.ts index a57c023d6..4cdd70c12 100644 --- a/docs/.vitepress/transformPageData.ts +++ b/docs/.vitepress/transformPageData.ts @@ -10,47 +10,54 @@ export async function transformPageData(pageData: PageData) { pageData.params?.name ) { const iconName = pageData.params.name; - pageData.title = `${iconName} icon details`; + const isDetailsPage = pageData.relativePath.includes('details'); - const taggedAs = pageData.params?.tags?.length - ? `Tagged as: ${pageData.params.tags.join(', ')}.` - : ''; - const categorizedIn = pageData.params?.category?.length - ? `Categorized in: ${pageData.params.category.join(', ')}.` - : ''; + if (isDetailsPage) { + pageData.title = `${iconName} icon details`; - pageData.description = - `Details and related icons for ${iconName} icon. ${taggedAs} ${categorizedIn}`.trim(); + const taggedAs = pageData.params?.tags?.length + ? `Tagged as: ${pageData.params.tags.join(', ')}.` + : ''; + const categorizedIn = pageData.params?.category?.length + ? `Categorized in: ${pageData.params.category.join(', ')}.` + : ''; - const structuredData = await getStructuredData(iconName, pageData); + pageData.description = + `Details and related icons for ${iconName} icon. ${taggedAs} ${categorizedIn}`.trim(); - const ogPath = await createIconOGImage(iconName, pageData.params?.tags || []); + const structuredData = await getStructuredData(iconName, pageData); - if (ogPath) { - const content = `https://lucide.dev${ogPath}`; - pageData.frontmatter.head.push( - [ - 'meta', - { - property: 'og:image', - content, - }, - ], - [ - 'meta', - { - property: 'twitter:image', - content, - }, - ], - ); + const ogPath = await createIconOGImage(iconName, pageData.params?.tags || []); + + if (ogPath) { + const content = `https://lucide.dev${ogPath}`; + pageData.frontmatter.head.push( + [ + 'meta', + { + property: 'og:image', + content, + }, + ], + [ + 'meta', + { + property: 'twitter:image', + content, + }, + ], + ); + } + + pageData.frontmatter.head.push([ + 'script', + { type: 'application/ld+json' }, + JSON.stringify(structuredData), + ]); + } else { + pageData.title = `${iconName} icon`; + pageData.description = `Browse and customize the ${iconName} icon.`; } - - pageData.frontmatter.head.push([ - 'script', - { type: 'application/ld+json' }, - JSON.stringify(structuredData), - ]); } if (pageData.relativePath.startsWith('guide/')) { diff --git a/docs/icons/[name].md b/docs/icons/[name].md index 9f88fe0e4..5f53d0a00 100644 --- a/docs/icons/[name].md +++ b/docs/icons/[name].md @@ -1,260 +1,24 @@ --- -layout: doc -footer: false -aside: false -editLink: false -next: false -prev: false +title: Icons +description: Browse all Lucide icons. +layout: page +outline: 2 +outlineTitle: Categories sidebar: true --- -
-
- -
-
- -
- -
-
- -
-
- - Unreleased -
-
- - - {{params.createdRelease.version}} - -
-
- - - {{params.changedRelease.version}} - -
- -
-
-
+
+ + +
- - - - - - - - - - - - - - diff --git a/docs/icons/[name].paths.ts b/docs/icons/[name].paths.ts index 4921fa5e3..1f38e9ed5 100644 --- a/docs/icons/[name].paths.ts +++ b/docs/icons/[name].paths.ts @@ -1,22 +1,11 @@ -import relatedIcons from '../.vitepress/data/relatedIcons.json'; import iconNodes from '../.vitepress/data/iconNodes'; -import * as iconDetails from '../.vitepress/data/iconDetails'; -import { IconEntity } from '../.vitepress/theme/types'; export default { paths: async () => { - return (Object.values(iconDetails) as unknown as IconEntity[]).map((iconEntity) => { - const params = { - ...iconEntity, - relatedIcons: relatedIcons[iconEntity.name].map((name: string) => ({ - name, - iconNode: iconNodes[name], - })), - }; - - return { - params, - }; - }); + return Object.keys(iconNodes).map((name) => ({ + params: { + name, + }, + })); }, }; diff --git a/docs/icons/lab/[name].md b/docs/icons/lab/[name].md index a3e215a52..03fa91cc3 100644 --- a/docs/icons/lab/[name].md +++ b/docs/icons/lab/[name].md @@ -1,10 +1,25 @@ --- -layout: doc -footer: false -aside: false -editLink: false -next: false -prev: false +title: Icons +description: Browse all Lucide icons. +layout: page +outline: 2 +outlineTitle: Categories sidebar: true --- - + + +
+ + + +
+ diff --git a/docs/icons/lab/[name].paths.ts b/docs/icons/lab/[name].paths.ts index be0528c10..10164d72f 100644 --- a/docs/icons/lab/[name].paths.ts +++ b/docs/icons/lab/[name].paths.ts @@ -1,25 +1,11 @@ import iconNodes from '../../.vitepress/data/lab/iconNodes'; -import iconPopularity from '../../.vitepress/data/lab/iconPopularity'; -import iconMetaData from '../../.vitepress/data/lab/iconMetaData'; export default { paths: async () => { - return Object.entries(iconNodes).map(([name, iconNode]) => { - const iconDetails = iconMetaData[name]; - - delete iconDetails['$schema']; - - const params = { + return Object.keys(iconNodes).map((name) => ({ + params: { name, - iconNode, - externalLibrary: 'lab', - popularity: iconPopularity[name]?.count ?? 0, - ...iconDetails, - }; - - return { - params, - }; - }); + }, + })); }, }; diff --git a/docs/scripts/writeVercelOutput.mts b/docs/scripts/writeVercelOutput.mts index f13c86cd8..954b83a79 100644 --- a/docs/scripts/writeVercelOutput.mts +++ b/docs/scripts/writeVercelOutput.mts @@ -10,16 +10,27 @@ const iconMetaData = await getIconMetaData(path.resolve(scriptDir, '../../icons' const iconAliasesRedirectRoutes = Object.entries(iconMetaData) .filter(([, { aliases }]) => aliases?.length) - .map(([iconName, { aliases }]) => { - const aliasRouteMatches = aliases.map((alias) => typeof alias === 'string' ? alias : alias?.name).join('|'); + .flatMap(([iconName, { aliases }]) => { + const aliasRouteMatches = aliases + .map((alias) => (typeof alias === 'string' ? alias : alias?.name)) + .join('|'); - return { - src: `^/icons/(${aliasRouteMatches})$`, - status: 308, - headers: { - Location: `/icons/${iconName}`, + return [ + { + src: `^/icons/(${aliasRouteMatches})$`, + status: 308, + headers: { + Location: `/icons/${iconName}`, + }, }, - }; + { + src: `^/icons/(${aliasRouteMatches})/details$`, + status: 308, + headers: { + Location: `/icons/${iconName}/details`, + }, + }, + ]; }); const vercelOutputJSON = path.resolve(currentDir, '.vercel/output/config.json');