[READ-ONLY] a fast, modern browser for the npm registry
at main 110 lines 3.4 kB view raw
1<script setup lang="ts"> 2import type { PackageFileTree } from '#shared/types' 3import type { RouteLocationRaw } from 'vue-router' 4import type { RouteNamedMap } from 'vue-router/auto-routes' 5import { ADDITIONAL_ICONS, getFileIcon } from '~/utils/file-icons' 6 7const props = defineProps<{ 8 tree: PackageFileTree[] 9 currentPath: string 10 baseUrl: string 11 baseRoute: Pick<RouteNamedMap['code'], 'params'> 12 depth?: number 13}>() 14 15const depth = computed(() => props.depth ?? 0) 16 17// Check if a node or any of its children is currently selected 18function isNodeActive(node: PackageFileTree): boolean { 19 if (props.currentPath === node.path) return true 20 if (props.currentPath.startsWith(node.path + '/')) return true 21 return false 22} 23 24// Build route object for a file path 25function getFileRoute(nodePath: string): RouteLocationRaw { 26 return { 27 name: 'code', 28 params: { 29 org: props.baseRoute.params.org, 30 packageName: props.baseRoute.params.packageName, 31 version: props.baseRoute.params.version, 32 filePath: nodePath ?? '', 33 }, 34 } 35} 36 37const { toggleDir, isExpanded, autoExpandAncestors } = useFileTreeState(props.baseUrl) 38 39// Auto-expand directories in the current path 40watch( 41 () => props.currentPath, 42 path => { 43 if (path) { 44 autoExpandAncestors(path) 45 } 46 }, 47 { immediate: true }, 48) 49</script> 50 51<template> 52 <ul class="list-none m-0 p-0" :class="depth === 0 ? 'py-2' : ''"> 53 <li v-for="node in tree" :key="node.path"> 54 <!-- Directory --> 55 <template v-if="node.type === 'directory'"> 56 <ButtonBase 57 class="w-full justify-start! rounded-none! border-none!" 58 block 59 :aria-pressed="isNodeActive(node)" 60 :style="{ paddingLeft: `${depth * 12 + 12}px` }" 61 @click="toggleDir(node.path)" 62 :classicon="isExpanded(node.path) ? 'i-lucide:chevron-down' : 'i-lucide:chevron-right'" 63 > 64 <svg 65 class="size-[1em] me-1 shrink-0" 66 :class="isExpanded(node.path) ? 'text-yellow-500' : 'text-yellow-600'" 67 viewBox="0 0 16 16" 68 fill="currentColor" 69 aria-hidden="true" 70 > 71 <use 72 :href="`/file-tree-sprite.svg#${isExpanded(node.path) ? ADDITIONAL_ICONS['folder-open'] : ADDITIONAL_ICONS['folder']}`" 73 /> 74 </svg> 75 <span class="truncate">{{ node.name }}</span> 76 </ButtonBase> 77 <CodeFileTree 78 v-if="isExpanded(node.path) && node.children" 79 :tree="node.children" 80 :current-path="currentPath" 81 :base-url="baseUrl" 82 :base-route="baseRoute" 83 :depth="depth + 1" 84 /> 85 </template> 86 87 <!-- File --> 88 <template v-else> 89 <LinkBase 90 variant="button-secondary" 91 :to="getFileRoute(node.path)" 92 :aria-current="currentPath === node.path" 93 class="w-full justify-start! rounded-none! border-none!" 94 block 95 :style="{ paddingLeft: `${depth * 12 + 32}px` }" 96 > 97 <svg 98 class="size-[1em] me-1 shrink-0" 99 viewBox="0 0 16 16" 100 fill="currentColor" 101 aria-hidden="true" 102 > 103 <use :href="`/file-tree-sprite.svg#${getFileIcon(node.name)}`" /> 104 </svg> 105 <span class="truncate">{{ node.name }}</span> 106 </LinkBase> 107 </template> 108 </li> 109 </ul> 110</template>