forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2import type { NuxtLinkProps } from '#app'
3
4const props = withDefaults(
5 defineProps<
6 {
7 /** Disabled links will be displayed as plain text */
8 disabled?: boolean
9 /**
10 * `type` should never be used, because this will always be a link.
11 * */
12 type?: never
13 variant?: 'button-primary' | 'button-secondary' | 'link'
14 size?: 'small' | 'medium'
15 block?: boolean
16
17 ariaKeyshortcuts?: string
18
19 /**
20 * Don't use this directly. This will automatically be set to `_blank` for external links passed via `to`.
21 */
22 target?: never
23
24 /**
25 * Don't use this directly. This will automatically be set for external links passed via `to`.
26 */
27 rel?: never
28
29 classicon?: string
30
31 to?: NuxtLinkProps['to']
32
33 /** always use `to` instead of `href` */
34 href?: never
35
36 /** should only be used for links where the context makes it very clear they are clickable. Don't just use this, because you don't like underlines. */
37 noUnderline?: boolean
38 } & NuxtLinkProps
39 >(),
40 { variant: 'link', size: 'medium' },
41)
42
43const isLinkExternal = computed(
44 () =>
45 !!props.to &&
46 typeof props.to === 'string' &&
47 (props.to.startsWith('http:') || props.to.startsWith('https:') || props.to.startsWith('//')),
48)
49const isLinkAnchor = computed(
50 () => !!props.to && typeof props.to === 'string' && props.to.startsWith('#'),
51)
52
53/** size is only applicable for button like links */
54const isLink = computed(() => props.variant === 'link')
55const isButton = computed(() => !isLink.value)
56const isButtonSmall = computed(() => props.size === 'small' && !isLink.value)
57const isButtonMedium = computed(() => props.size === 'medium' && !isLink.value)
58</script>
59
60<template>
61 <span
62 v-if="disabled"
63 :class="{
64 'flex': block,
65 'inline-flex': !block,
66 'opacity-50 gap-x-1 items-center justify-center font-mono border border-transparent rounded-md':
67 isButton,
68 'text-sm px-4 py-2': isButtonMedium,
69 'text-xs px-2 py-0.5': isButtonSmall,
70 'text-bg bg-fg': variant === 'button-primary',
71 'bg-transparent text-fg': variant === 'button-secondary',
72 }"
73 ><slot
74 /></span>
75 <NuxtLink
76 v-bind="props"
77 v-else
78 class="group/link gap-x-1 items-center"
79 :class="{
80 'flex': block,
81 'inline-flex': !block,
82 'underline-offset-[0.2rem] underline decoration-1 decoration-fg/30':
83 !isLinkAnchor && isLink && !noUnderline,
84 'justify-start font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200':
85 isLink,
86 'justify-center font-mono border border-border rounded-md transition-all duration-200':
87 isButton,
88 'text-sm px-4 py-2': isButtonMedium,
89 'text-xs px-2 py-0.5': isButtonSmall,
90 'bg-transparent text-fg hover:(bg-fg/10 text-accent) focus-visible:(bg-fg/10 text-accent) aria-[current=true]:(bg-fg/10 text-accent border-fg/20 hover:enabled:(bg-fg/20 text-fg/50))':
91 variant === 'button-secondary',
92 'text-bg bg-fg hover:(bg-fg/50 text-accent) focus-visible:(bg-fg/50) aria-current:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
93 variant === 'button-primary',
94 }"
95 :to="to"
96 :aria-keyshortcuts="ariaKeyshortcuts"
97 :target="isLinkExternal ? '_blank' : undefined"
98 >
99 <span v-if="classicon" class="size-[1em]" :class="classicon" aria-hidden="true" />
100 <slot />
101 <!-- automatically show icon indicating external link -->
102 <span
103 v-if="isLinkExternal && !classicon"
104 class="i-lucide:external-link rtl-flip size-[1em] opacity-50"
105 aria-hidden="true"
106 />
107 <span
108 v-else-if="isLinkAnchor && isLink"
109 class="i-lucide:link size-[1em] opacity-0 group-hover/link:opacity-100 transition-opacity duration-200"
110 aria-hidden="true"
111 />
112 <kbd
113 v-if="ariaKeyshortcuts"
114 class="ms-2 inline-flex items-center justify-center size-4 text-xs text-fg bg-bg-muted border border-border rounded no-underline"
115 aria-hidden="true"
116 >
117 {{ ariaKeyshortcuts }}
118 </kbd>
119 </NuxtLink>
120</template>