Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 141 additions & 143 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
@@ -1,165 +1,163 @@
<script setup lang="ts">
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
import { footerSections } from '~/utils/footer-navigation'

const discord = useDiscordLink()
const route = useRoute()
const isHome = computed(() => route.name === 'index')

const discord = useDiscordLink()
const { commandPaletteShortcutLabel } = usePlatformModifierKey()
const modalRef = useTemplateRef('modalRef')
const showModal = () => modalRef.value?.showModal?.()
const closeModal = () => modalRef.value?.close?.()

const socialLinks = computed(() => [
{
id: 'github',
href: 'https://repo.npmx.dev',
icon: 'i-simple-icons:github',
label: $t('footer.source'),
},
{
id: 'discord',
href: discord.value.url,
icon: 'i-simple-icons:discord',
label: discord.value.label,
},
{
id: 'bluesky',
href: 'https://social.npmx.dev',
icon: 'i-simple-icons:bluesky',
label: $t('footer.social'),
},
])
</script>

<template>
<footer class="border-t border-border mt-auto">
<div class="container py-3 sm:py-8 flex flex-col gap-2 sm:gap-4 text-fg-subtle text-sm">
<div class="flex flex-col lg:flex-row lg:items-baseline justify-between gap-2 sm:gap-4">
<div>
<p class="font-mono text-balance m-0 hidden sm:block mb-3">
{{ $t('tagline') }}
</p>
<footer class="border-t border-transparent lg:border-border lg:mt-auto">
<div class="container flex flex-col text-sm text-fg-subtle sm:gap-2.5 sm:px-3 sm:pt-8">
<div class="hidden lg:flex justify-between py-3 gap-15">
<div class="flex flex-col gap-3 w-full">
<AppLogo class="text-white h-7 w-fit" />
<BuildEnvironment v-if="!isHome" footer />
</div>
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
<div class="hidden sm:flex flex-col lg:items-end gap-3 min-h-11 text-xs">
<div class="flex items-center gap-5">
<LinkBase :to="{ name: 'about' }">
{{ $t('footer.about') }}
</LinkBase>
<LinkBase :to="{ name: 'blog' }">
{{ $t('footer.blog') }}
</LinkBase>
<LinkBase :to="{ name: 'privacy' }">
{{ $t('privacy_policy.title') }}
</LinkBase>
<LinkBase :to="{ name: 'accessibility' }">
{{ $t('a11y.footer_title') }}
</LinkBase>
<LinkBase :to="{ name: 'translation-status' }">
{{ $t('translation_status.title') }}
</LinkBase>
<LinkBase :to="{ name: 'brand' }">
{{ $t('footer.brand') }}
</LinkBase>
<button
type="button"
class="cursor-pointer group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
@click.prevent="showModal"
aria-haspopup="dialog"
<div class="flex items-center gap-3">
<a
v-for="link in socialLinks"
:key="link.id"
:href="link.href"
:aria-label="link.label"
:title="link.label"
target="_blank"
rel="noopener noreferrer"
class="inline-flex h-7 w-7 items-center justify-center rounded-md text-fg-subtle transition-colors duration-200 hover:text-fg focus-visible:text-fg"
>
{{ $t('footer.keyboard_shortcuts') }}
</button>

<Modal
id="keyboard-shortcuts-modal"
ref="modalRef"
:modalTitle="$t('footer.keyboard_shortcuts')"
class="w-auto max-w-lg"
>
<p class="mb-4 text-sm leading-relaxed text-fg-muted">
{{
$t('shortcuts.command_palette_description', { ctrlKey: $t('shortcuts.ctrl_key') })
}}
</p>
<p class="mb-2 font-mono text-fg-subtle">
{{ $t('shortcuts.section.global') }}
</p>
<ul class="mb-6 flex flex-col gap-2">
<li class="flex gap-2 items-center">
<kbd class="kbd">{{ commandPaletteShortcutLabel }}</kbd>
<span>{{ $t('shortcuts.command_palette') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">/</kbd>
<span>{{ $t('shortcuts.focus_search') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">?</kbd>
<span>{{ $t('shortcuts.show_kbd_hints') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">,</kbd>
<span>{{ $t('shortcuts.settings') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">c</kbd>
<span>{{ $t('shortcuts.compare') }}</span>
</li>
</ul>
<p class="mb-2 font-mono text-fg-subtle">
{{ $t('shortcuts.section.search') }}
</p>
<ul class="mb-6 flex flex-col gap-2">
<li class="flex gap-2 items-center">
<kbd class="kbd">↑</kbd>/<kbd class="kbd">↓</kbd>
<span>{{ $t('shortcuts.navigate_results') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">Enter</kbd>
<span>{{ $t('shortcuts.go_to_result') }}</span>
</li>
</ul>
<p class="mb-2 font-mono text-fg-subtle">
{{ $t('shortcuts.section.package') }}
</p>
<ul class="mb-8 flex flex-col gap-2">
<li class="flex gap-2 items-center">
<kbd class="kbd">m</kbd>
<span>{{ $t('shortcuts.open_main') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">d</kbd>
<span>{{ $t('shortcuts.open_docs') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">.</kbd>
<span>{{ $t('shortcuts.open_code_view') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">f</kbd>
<span>{{ $t('shortcuts.open_diff') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">c</kbd>
<span>{{ $t('shortcuts.compare_from_package') }}</span>
</li>
</ul>
<p class="text-fg-muted leading-relaxed">
<i18n-t keypath="shortcuts.disable_shortcuts" tag="span" scope="global">
<template #settings>
<NuxtLink
:to="{ name: 'settings' }"
class="hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
@click="closeModal"
>
{{ $t('settings.title') }}
</NuxtLink>
</template>
</i18n-t>
</p>
</Modal>
<span :class="link.icon" class="h-7 w-7" aria-hidden="true" />
</a>
</div>
<div class="flex items-center gap-5">
<LinkBase :to="NPMX_DOCS_SITE">
{{ $t('footer.docs') }}
</LinkBase>
<LinkBase to="https://repo.npmx.dev">
{{ $t('footer.source') }}
</LinkBase>
<LinkBase to="https://social.npmx.dev">
{{ $t('footer.social') }}
</LinkBase>
<LinkBase :to="discord.url">
{{ discord.label }}
</LinkBase>
</div>
<div class="flex flex-col justify-between gap-3 lg:flex-row">
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
<div class="flex gap-8">
<div
v-for="(section, index) in footerSections"
:key="index"
class="flex flex-col gap-4 min-w-[152px]"
>
<p class="uppercase font-mono">{{ $t(section.title) }}</p>
<div class="flex flex-col gap-2">
<template v-for="(item, itemIndex) in section.items" :key="itemIndex">
<button
v-if="item?.btn"
type="button"
class="cursor-pointer inline-flex w-fit items-center justify-start rounded-md px-2 py-1 font-mono text-fg-subtle transition-colors duration-200 hover:(bg-bg-subtle text-fg) focus-visible:(bg-bg-subtle text-fg)"
@click.prevent="showModal"
aria-haspopup="dialog"
>
{{ $t(item.i18n) }}
</button>
<LinkBase v-else :to="item?.to" variant="footer" class="w-fit">
{{ $t(item.i18n ?? '') || item.i18n }}
</LinkBase>
</template>
</div>
</div>
</div>
<Modal
ref="modalRef"
:modalTitle="$t('footer.keyboard_shortcuts')"
class="w-auto max-w-lg"
>
<p class="mb-2 font-mono text-fg-subtle">
{{ $t('shortcuts.section.global') }}
</p>
<ul class="mb-6 flex flex-col gap-2">
<li class="flex gap-2 items-center">
<kbd class="kbd">/</kbd>
<span>{{ $t('shortcuts.focus_search') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">?</kbd>
<span>{{ $t('shortcuts.show_kbd_hints') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">,</kbd>
<span>{{ $t('shortcuts.settings') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">c</kbd>
<span>{{ $t('shortcuts.compare') }}</span>
</li>
</ul>
<p class="mb-2 font-mono text-fg-subtle">
{{ $t('shortcuts.section.search') }}
</p>
<ul class="mb-6 flex flex-col gap-2">
<li class="flex gap-2 items-center">
<kbd class="kbd">↑</kbd>/<kbd class="kbd">↓</kbd>
<span>{{ $t('shortcuts.navigate_results') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">Enter</kbd>
<span>{{ $t('shortcuts.go_to_result') }}</span>
</li>
</ul>
<p class="mb-2 font-mono text-fg-subtle">
{{ $t('shortcuts.section.package') }}
</p>
<ul class="mb-8 flex flex-col gap-2">
<li class="flex gap-2 items-center">
<kbd class="kbd">.</kbd>
<span>{{ $t('shortcuts.open_code_view') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">d</kbd>
<span>{{ $t('shortcuts.open_docs') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">c</kbd>
<span>{{ $t('shortcuts.compare_from_package') }}</span>
</li>
</ul>
<p class="text-fg-muted leading-relaxed">
<i18n-t keypath="shortcuts.disable_shortcuts" tag="span" scope="global">
<template #settings>
<NuxtLink
:to="{ name: 'settings' }"
class="hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
@click="closeModal"
>
{{ $t('settings.title') }}
</NuxtLink>
</template>
</i18n-t>
</p>
</Modal>
</div>
</div>
<small class="text-xs text-fg-muted text-center sm:text-start m-0">
<p
class="text-xs text-fg-muted text-center sm:text-start border-t border-border mx-auto w-full py-4"
>
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>
</small>
</p>
</div>
</footer>
</template>
Expand Down
2 changes: 1 addition & 1 deletion app/components/BuildEnvironment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const buildTime = computed(() => new Date(buildInfo.value.time))
<template>
<div
class="font-mono text-xs text-fg-muted flex items-center gap-2 motion-safe:animate-fade-in motion-safe:animate-fill-both"
:class="footer ? 'my-1 justify-center sm:justify-start' : 'mb-8 justify-center'"
:class="footer ? 'hidden sm:flex sm:justify-start' : 'mb-8 justify-center'"
style="animation-delay: 0.05s"
>
<i18n-t keypath="built_at" scope="global">
Expand Down
11 changes: 7 additions & 4 deletions app/components/Link/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const props = withDefaults(
* */
type?: never
/** Visual style of the link */
variant?: 'button-primary' | 'button-secondary' | 'link'
variant?: 'button-primary' | 'button-secondary' | 'link' | 'footer'
/** Size (only applicable for button variants) */
size?: 'sm' | 'md'
/** Makes the link take full width */
Expand Down Expand Up @@ -62,7 +62,8 @@ const isLinkAnchor = computed(
)

/** size is only applicable for button like links */
const isLink = computed(() => props.variant === 'link')
const isFooterLink = computed(() => props.variant === 'footer')
const isLink = computed(() => props.variant === 'link' || isFooterLink.value)
const isButton = computed(() => !isLink.value)
const isButtonSmall = computed(() => props.size === 'sm' && !isLink.value)
const isButtonMedium = computed(() => props.size === 'md' && !isLink.value)
Expand Down Expand Up @@ -92,9 +93,11 @@ const keyboardShortcutsEnabled = useKeyboardShortcuts()
'flex': block,
'inline-flex': !block,
'underline-offset-[0.2rem] underline decoration-1 decoration-fg/30':
!isLinkAnchor && isLink && !noUnderline,
!isLinkAnchor && isLink && !isFooterLink && !noUnderline,
'justify-start font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200':
isLink,
variant === 'link',
'justify-start rounded-md px-2 py-1 font-mono text-fg-subtle transition-colors duration-200 hover:(bg-bg-subtle text-fg) focus-visible:(bg-bg-subtle text-fg)':
isFooterLink,
'justify-center font-mono border border-border rounded-md transition-all duration-200':
isButton,
'text-sm px-4 py-2': isButtonMedium,
Expand Down
8 changes: 8 additions & 0 deletions app/components/Link/Link.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export const ButtonSecondary: Story = {
},
}

export const FooterLink: Story = {
args: {
variant: 'footer',
default: 'blog',
},
}

export const SmallButton: Story = {
args: {
variant: 'button-primary',
Expand Down Expand Up @@ -127,6 +134,7 @@ export const Snapshot: Story = {
<LinkBase to="#section">Anchor Link</LinkBase>
<LinkBase to="/" classicon="i-lucide:check">Link with icon</LinkBase>
<LinkBase to="/" no-underline>Link without underline</LinkBase>
<LinkBase to="/" variant="footer">blog</LinkBase>
<LinkBase to="/" disabled>Disabled Link</LinkBase>

<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
Expand Down
Loading
Loading