Skip to content

Commit dfaed08

Browse files
committed
refactor: unify footer navigation styles and config
1 parent c8ed119 commit dfaed08

File tree

6 files changed

+219
-127
lines changed

6 files changed

+219
-127
lines changed

app/components/AppFooter.vue

Lines changed: 143 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,163 @@
11
<script setup lang="ts">
2-
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
2+
import { footerSections } from '~/utils/footer-navigation'
33
4+
const discord = useDiscordLink()
45
const route = useRoute()
56
const isHome = computed(() => route.name === 'index')
67
7-
const discord = useDiscordLink()
88
const modalRef = useTemplateRef('modalRef')
99
const showModal = () => modalRef.value?.showModal?.()
1010
const closeModal = () => modalRef.value?.close?.()
11+
12+
const socialLinks = computed(() => [
13+
{
14+
id: 'github',
15+
href: 'https://repo.npmx.dev',
16+
icon: 'i-simple-icons:github',
17+
label: $t('footer.source'),
18+
},
19+
{
20+
id: 'discord',
21+
href: discord.value.url,
22+
icon: 'i-simple-icons:discord',
23+
label: discord.value.label,
24+
},
25+
{
26+
id: 'bluesky',
27+
href: 'https://social.npmx.dev',
28+
icon: 'i-simple-icons:bluesky',
29+
label: $t('footer.social'),
30+
},
31+
])
1132
</script>
1233

1334
<template>
14-
<footer class="border-t border-border mt-auto">
15-
<div class="container sm:py-8 flex flex-col sm:gap-4 text-fg-subtle text-sm">
16-
<div class="flex justify-between">
17-
<p class="font-mono text-balance hidden sm:block">
18-
{{ $t('tagline') }}
19-
</p>
20-
</div>
21-
<div class="flex justify-between flex-col lg:flex-row gap-3">
22-
<div class="flex flex-col gap-3">
23-
<p class="text-xs text-fg-muted text-center sm:text-start m-0">
24-
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
25-
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>
26-
</p>
35+
<footer class="border-t border-transparent lg:border-border lg:mt-auto">
36+
<div class="container flex flex-col text-sm text-fg-subtle sm:gap-2.5 sm:px-3 sm:pt-8">
37+
<div class="hidden lg:flex justify-between py-3 gap-15">
38+
<div class="flex flex-col gap-3 w-full">
39+
<AppLogo class="text-white h-7 w-fit" />
2740
<BuildEnvironment v-if="!isHome" footer />
28-
</div>
29-
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
30-
<div class="hidden sm:flex flex-col lg:items-end gap-3 min-h-11 text-xs">
31-
<div class="flex items-center gap-5">
32-
<LinkBase :to="{ name: 'about' }">
33-
{{ $t('footer.about') }}
34-
</LinkBase>
35-
<LinkBase :to="{ name: 'blog' }">
36-
{{ $t('footer.blog') }}
37-
</LinkBase>
38-
<LinkBase :to="{ name: 'privacy' }">
39-
{{ $t('privacy_policy.title') }}
40-
</LinkBase>
41-
<LinkBase :to="{ name: 'accessibility' }">
42-
{{ $t('a11y.footer_title') }}
43-
</LinkBase>
44-
<button
45-
type="button"
46-
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"
47-
@click.prevent="showModal"
48-
aria-haspopup="dialog"
41+
<div class="flex items-center gap-3">
42+
<a
43+
v-for="link in socialLinks"
44+
:key="link.id"
45+
:href="link.href"
46+
:aria-label="link.label"
47+
:title="link.label"
48+
target="_blank"
49+
rel="noopener noreferrer"
50+
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"
4951
>
50-
{{ $t('footer.keyboard_shortcuts') }}
51-
</button>
52-
53-
<Modal
54-
ref="modalRef"
55-
:modalTitle="$t('footer.keyboard_shortcuts')"
56-
class="w-auto max-w-lg"
57-
>
58-
<p class="mb-2 font-mono text-fg-subtle">
59-
{{ $t('shortcuts.section.global') }}
60-
</p>
61-
<ul class="mb-6 flex flex-col gap-2">
62-
<li class="flex gap-2 items-center">
63-
<kbd class="kbd">/</kbd>
64-
<span>{{ $t('shortcuts.focus_search') }}</span>
65-
</li>
66-
<li class="flex gap-2 items-center">
67-
<kbd class="kbd">?</kbd>
68-
<span>{{ $t('shortcuts.show_kbd_hints') }}</span>
69-
</li>
70-
<li class="flex gap-2 items-center">
71-
<kbd class="kbd">,</kbd>
72-
<span>{{ $t('shortcuts.settings') }}</span>
73-
</li>
74-
<li class="flex gap-2 items-center">
75-
<kbd class="kbd">c</kbd>
76-
<span>{{ $t('shortcuts.compare') }}</span>
77-
</li>
78-
</ul>
79-
<p class="mb-2 font-mono text-fg-subtle">
80-
{{ $t('shortcuts.section.search') }}
81-
</p>
82-
<ul class="mb-6 flex flex-col gap-2">
83-
<li class="flex gap-2 items-center">
84-
<kbd class="kbd">↑</kbd>/<kbd class="kbd">↓</kbd>
85-
<span>{{ $t('shortcuts.navigate_results') }}</span>
86-
</li>
87-
<li class="flex gap-2 items-center">
88-
<kbd class="kbd">Enter</kbd>
89-
<span>{{ $t('shortcuts.go_to_result') }}</span>
90-
</li>
91-
</ul>
92-
<p class="mb-2 font-mono text-fg-subtle">
93-
{{ $t('shortcuts.section.package') }}
94-
</p>
95-
<ul class="mb-8 flex flex-col gap-2">
96-
<li class="flex gap-2 items-center">
97-
<kbd class="kbd">.</kbd>
98-
<span>{{ $t('shortcuts.open_code_view') }}</span>
99-
</li>
100-
<li class="flex gap-2 items-center">
101-
<kbd class="kbd">d</kbd>
102-
<span>{{ $t('shortcuts.open_docs') }}</span>
103-
</li>
104-
<li class="flex gap-2 items-center">
105-
<kbd class="kbd">c</kbd>
106-
<span>{{ $t('shortcuts.compare_from_package') }}</span>
107-
</li>
108-
</ul>
109-
<p class="text-fg-muted leading-relaxed">
110-
<i18n-t keypath="shortcuts.disable_shortcuts" tag="span" scope="global">
111-
<template #settings>
112-
<NuxtLink
113-
:to="{ name: 'settings' }"
114-
class="hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
115-
@click="closeModal"
116-
>
117-
{{ $t('settings.title') }}
118-
</NuxtLink>
119-
</template>
120-
</i18n-t>
121-
</p>
122-
</Modal>
52+
<span :class="link.icon" class="h-7 w-7" aria-hidden="true" />
53+
</a>
12354
</div>
124-
<div class="flex items-center gap-5">
125-
<LinkBase :to="NPMX_DOCS_SITE">
126-
{{ $t('footer.docs') }}
127-
</LinkBase>
128-
<LinkBase to="https://repo.npmx.dev">
129-
{{ $t('footer.source') }}
130-
</LinkBase>
131-
<LinkBase to="https://social.npmx.dev">
132-
{{ $t('footer.social') }}
133-
</LinkBase>
134-
<LinkBase :to="discord.url">
135-
{{ discord.label }}
136-
</LinkBase>
55+
</div>
56+
<div class="flex flex-col justify-between gap-3 lg:flex-row">
57+
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
58+
<div class="flex gap-8">
59+
<div
60+
v-for="(section, index) in footerSections"
61+
:key="index"
62+
class="flex flex-col gap-4 min-w-[152px]"
63+
>
64+
<p class="uppercase font-mono">{{ $t(section.title) }}</p>
65+
<div class="flex flex-col gap-2">
66+
<template v-for="(item, itemIndex) in section.items" :key="itemIndex">
67+
<button
68+
v-if="item?.btn"
69+
type="button"
70+
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)"
71+
@click.prevent="showModal"
72+
aria-haspopup="dialog"
73+
>
74+
{{ $t(item.i18n) }}
75+
</button>
76+
<LinkBase v-else :to="item?.to" variant="footer" class="w-fit">
77+
{{ $t(item.i18n ?? '') || item.i18n }}
78+
</LinkBase>
79+
</template>
80+
</div>
81+
</div>
13782
</div>
83+
<Modal
84+
ref="modalRef"
85+
:modalTitle="$t('footer.keyboard_shortcuts')"
86+
class="w-auto max-w-lg"
87+
>
88+
<p class="mb-2 font-mono text-fg-subtle">
89+
{{ $t('shortcuts.section.global') }}
90+
</p>
91+
<ul class="mb-6 flex flex-col gap-2">
92+
<li class="flex gap-2 items-center">
93+
<kbd class="kbd">/</kbd>
94+
<span>{{ $t('shortcuts.focus_search') }}</span>
95+
</li>
96+
<li class="flex gap-2 items-center">
97+
<kbd class="kbd">?</kbd>
98+
<span>{{ $t('shortcuts.show_kbd_hints') }}</span>
99+
</li>
100+
<li class="flex gap-2 items-center">
101+
<kbd class="kbd">,</kbd>
102+
<span>{{ $t('shortcuts.settings') }}</span>
103+
</li>
104+
<li class="flex gap-2 items-center">
105+
<kbd class="kbd">c</kbd>
106+
<span>{{ $t('shortcuts.compare') }}</span>
107+
</li>
108+
</ul>
109+
<p class="mb-2 font-mono text-fg-subtle">
110+
{{ $t('shortcuts.section.search') }}
111+
</p>
112+
<ul class="mb-6 flex flex-col gap-2">
113+
<li class="flex gap-2 items-center">
114+
<kbd class="kbd">↑</kbd>/<kbd class="kbd">↓</kbd>
115+
<span>{{ $t('shortcuts.navigate_results') }}</span>
116+
</li>
117+
<li class="flex gap-2 items-center">
118+
<kbd class="kbd">Enter</kbd>
119+
<span>{{ $t('shortcuts.go_to_result') }}</span>
120+
</li>
121+
</ul>
122+
<p class="mb-2 font-mono text-fg-subtle">
123+
{{ $t('shortcuts.section.package') }}
124+
</p>
125+
<ul class="mb-8 flex flex-col gap-2">
126+
<li class="flex gap-2 items-center">
127+
<kbd class="kbd">.</kbd>
128+
<span>{{ $t('shortcuts.open_code_view') }}</span>
129+
</li>
130+
<li class="flex gap-2 items-center">
131+
<kbd class="kbd">d</kbd>
132+
<span>{{ $t('shortcuts.open_docs') }}</span>
133+
</li>
134+
<li class="flex gap-2 items-center">
135+
<kbd class="kbd">c</kbd>
136+
<span>{{ $t('shortcuts.compare_from_package') }}</span>
137+
</li>
138+
</ul>
139+
<p class="text-fg-muted leading-relaxed">
140+
<i18n-t keypath="shortcuts.disable_shortcuts" tag="span" scope="global">
141+
<template #settings>
142+
<NuxtLink
143+
:to="{ name: 'settings' }"
144+
class="hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
145+
@click="closeModal"
146+
>
147+
{{ $t('settings.title') }}
148+
</NuxtLink>
149+
</template>
150+
</i18n-t>
151+
</p>
152+
</Modal>
138153
</div>
139154
</div>
155+
<p
156+
class="text-xs text-fg-muted text-center sm:text-start border-t border-border mx-auto w-full py-4"
157+
>
158+
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
159+
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>
160+
</p>
140161
</div>
141162
</footer>
142163
</template>

app/components/Link/Base.vue

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const props = withDefaults(
1111
* */
1212
type?: never
1313
/** Visual style of the link */
14-
variant?: 'button-primary' | 'button-secondary' | 'link'
14+
variant?: 'button-primary' | 'button-secondary' | 'link' | 'footer'
1515
/** Size (only applicable for button variants) */
1616
size?: 'small' | 'medium'
1717
/** Makes the link take full width */
@@ -62,7 +62,8 @@ const isLinkAnchor = computed(
6262
)
6363
6464
/** size is only applicable for button like links */
65-
const isLink = computed(() => props.variant === 'link')
65+
const isFooterLink = computed(() => props.variant === 'footer')
66+
const isLink = computed(() => props.variant === 'link' || isFooterLink.value)
6667
const isButton = computed(() => !isLink.value)
6768
const isButtonSmall = computed(() => props.size === 'small' && !isLink.value)
6869
const isButtonMedium = computed(() => props.size === 'medium' && !isLink.value)
@@ -92,9 +93,11 @@ const keyboardShortcutsEnabled = useKeyboardShortcuts()
9293
'flex': block,
9394
'inline-flex': !block,
9495
'underline-offset-[0.2rem] underline decoration-1 decoration-fg/30':
95-
!isLinkAnchor && isLink && !noUnderline,
96+
!isLinkAnchor && isLink && !isFooterLink && !noUnderline,
9697
'justify-start font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200':
97-
isLink,
98+
variant === 'link',
99+
'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)':
100+
isFooterLink,
98101
'justify-center font-mono border border-border rounded-md transition-all duration-200':
99102
isButton,
100103
'text-sm px-4 py-2': isButtonMedium,

app/components/Link/Link.stories.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ export const ButtonSecondary: Story = {
6363
},
6464
}
6565

66+
export const FooterLink: Story = {
67+
args: {
68+
variant: 'footer',
69+
default: 'blog',
70+
},
71+
}
72+
6673
export const SmallButton: Story = {
6774
args: {
6875
variant: 'button-primary',
@@ -112,6 +119,7 @@ export const Snapshot: Story = {
112119
<LinkBase to="#section">Anchor Link</LinkBase>
113120
<LinkBase to="/" classicon="i-lucide:check">Link with icon</LinkBase>
114121
<LinkBase to="/" no-underline>Link without underline</LinkBase>
122+
<LinkBase to="/" variant="footer">blog</LinkBase>
115123
<LinkBase to="/" disabled>Disabled Link</LinkBase>
116124
117125
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">

app/utils/footer-navigation.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
2+
import type { RouteLocationRaw } from 'vue-router'
3+
4+
export interface FooterItem {
5+
to?: RouteLocationRaw
6+
i18n: string
7+
btn?: boolean
8+
}
9+
10+
export interface FooterSection {
11+
title: string
12+
items: FooterItem[]
13+
}
14+
15+
export const footerSections: FooterSection[] = [
16+
{
17+
title: 'footer.sections.resources',
18+
items: [
19+
{ to: { name: 'blog' }, i18n: 'footer.blog' },
20+
{ to: { name: 'about' }, i18n: 'footer.about' },
21+
{ to: { name: 'accessibility' }, i18n: 'a11y.footer_title' },
22+
{ to: { name: 'privacy' }, i18n: 'privacy_policy.title' },
23+
],
24+
},
25+
{
26+
title: 'footer.sections.features',
27+
items: [
28+
{ to: { name: 'compare' }, i18n: 'shortcuts.compare' },
29+
{ to: { name: 'settings' }, i18n: 'shortcuts.settings' },
30+
{ i18n: 'footer.keyboard_shortcuts', btn: true },
31+
],
32+
},
33+
{
34+
title: 'footer.sections.other',
35+
items: [
36+
{ to: { name: 'pds' }, i18n: 'pds.title' },
37+
{ to: NPMX_DOCS_SITE, i18n: 'footer.docs' },
38+
],
39+
},
40+
]

i18n/locales/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919
"social": "social",
2020
"chat": "chat",
2121
"builders_chat": "builders",
22-
"keyboard_shortcuts": "keyboard shortcuts"
22+
"keyboard_shortcuts": "keyboard shortcuts",
23+
"sections": {
24+
"resources": "Resources",
25+
"features": "Features",
26+
"other": "Other"
27+
}
2328
},
2429
"shortcuts": {
2530
"section": {

0 commit comments

Comments
 (0)