Skip to content

Commit 0e1a7ca

Browse files
committed
feat: add BaseNavigationLeft (mobile navigation) for product landing page
See: DF-91
1 parent 06763da commit 0e1a7ca

File tree

15 files changed

+352
-67
lines changed

15 files changed

+352
-67
lines changed

frontend/app/router.options.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

frontend/i18n/locales/en.json

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
"base": {
33
"beaconchain_homepage": "Beaconchain Homepage",
44
"common": {
5-
"log_in": "Log in"
6-
},
7-
"products": {
8-
"api": "API",
9-
"explorer": "Explorer",
10-
"stacking_hub": "StackingHub"
5+
"close": "Close",
6+
"log_in": "Log in",
7+
"open_navigation": "Open Navigation",
8+
"side_navigation": "Side Navigation"
119
}
1210
},
1311
"block": {
@@ -663,7 +661,7 @@
663661
"register": "Sign up",
664662
"settings": "Account Settings",
665663
"slot": "Slot"
666-
},
664+
},
667665
"login_and_register": {
668666
"already_have_account": "Already have an account?",
669667
"choose_password": "Choose a password",
@@ -1119,6 +1117,25 @@
11191117
"view_addons": "View Add-Ons",
11201118
"yearly": "Yearly"
11211119
},
1120+
"products": {
1121+
"api": "API",
1122+
"explorer": "Explorer",
1123+
"landing_page": {
1124+
"api": {
1125+
"title": "Unified API to access data on"
1126+
},
1127+
"explorer": {
1128+
"title": "Seamless access to blockchain data on"
1129+
},
1130+
"search": {
1131+
"title": "Explore Transactions & Validator Staking on"
1132+
},
1133+
"stacking_hub": {
1134+
"title": "Comprehensive staking insights on"
1135+
}
1136+
},
1137+
"stacking_hub": "StackingHub"
1138+
},
11221139
"search_bar": {
11231140
"account_placeholder": "Address or ENS name",
11241141
"all_networks": "All",

frontend/layers/base/app/assets/css/main.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@
3434
--color-gray-300: #A8A8A8;
3535
--color-gray-700: #3B3B3B;
3636
--color-gray-900: #262626;
37-
37+
38+
--container-3xs: 16rem; /* 256px */
3839
--container-8xl: 90rem; /* 1440px */
3940

41+
/* --ease-in: cubic-bezier(0.4, 0, 1, 1); */
42+
/* --ease-out: cubic-bezier(0, 0, 0.2, 1); */
43+
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
44+
4045
--font-urbanist: "Urbanist", sans-serif;
4146
--font-weight-semibold: 600;
4247

48+
--radius-xl: .75rem; /* 12px */
4349
--radius-full: 2rem;
4450

4551
--spacing-sm: .375rem; /* 6px */
@@ -56,3 +62,7 @@
5662
--text-md: 1rem; /* 16px */
5763
--text-md--line-height: 1.5rem; /* 24px */
5864
}
65+
html {
66+
transition-behavior: allow-discrete;
67+
interpolate-size: allow-keywords;
68+
}

frontend/layers/base/app/components/BaseButtonIcon.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { IconName } from '~/layers/base/app/components/BaseIcon.vue'
33
44
const { size = 'md' } = defineProps<{
55
name: IconName,
6+
screenreaderText: TranslationInput,
67
size?: 'md',
78
variant: 'secondary',
89
}>()
@@ -12,10 +13,11 @@ const { size = 'md' } = defineProps<{
1213
<button
1314
class="flex rounded-full bg-linear-to-b disabled:opacity-40 aria-disabled:opacity-40 active:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300"
1415
:class="[
15-
variant === 'secondary' && 'from-gray-300 to-gray-200 text-black dark:from-charcoal-600 dark:to-charcoal-700 dark:text-white opacity-90 ',
16+
variant === 'secondary' && 'from-gray-300 to-gray-200 dark:from-charcoal-600 dark:to-charcoal-700 opacity-90 ',
1617
size === 'md' && 'p-md',
1718
]"
1819
>
20+
<BaseScreenreaderOnly :screenreader-text />
1921
<BaseIcon
2022
:name
2123
class="w-6 h-6"

frontend/layers/base/app/components/BaseNavigation.vue

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,53 @@
11
<script setup lang="ts">
2+
import type { BaseNavigationItem } from '#layers/base/app/components/BaseNavigationItem.vue'
3+
24
const { navigateToV1Login } = useV1Login()
35
const { t: $t } = useTranslation()
6+
const emit = defineEmits<{
7+
(e: 'open'): void,
8+
}>()
9+
const handleClick = () => {
10+
emit('open')
11+
}
12+
defineProps<{
13+
items: BaseNavigationItem[],
14+
}>()
415
</script>
516

617
<template>
718
<nav class="p-2xl">
819
<div class="max-w-8xl mx-auto grid grid-cols-[auto_1fr_auto] gap-2xl items-center">
920
<BaseButtonIcon
21+
screenreader-text="base.common.open_navigation"
1022
class="sm:hidden"
1123
name="menu-2"
1224
variant="secondary"
25+
@click="handleClick"
1326
/>
14-
<NuxtLink to="/">
15-
<span class="sr-only">
16-
{{ $t('base.beaconchain_homepage') }}
17-
</span>
18-
<span class="flex gap-sm text-black dark:text-white">
19-
<TheLogoMark
20-
width="1.25rem"
21-
class="aspect-square"
22-
/>
23-
<TheLogoType
24-
class="hidden md:inline"
25-
width="6.25rem"
26-
/>
27-
</span>
27+
<NuxtLink
28+
to="/"
29+
class="flex gap-sm p-md rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300 w-fit"
30+
>
31+
<BaseScreenreaderOnly screenreader-text="base.beaconchain_homepage" />
32+
<TheLogoMark
33+
width="1.25rem"
34+
class="aspect-square"
35+
/>
36+
<TheLogoType
37+
class="hidden md:inline"
38+
width="6.25rem"
39+
/>
2840
</NuxtLink>
2941
<ul class="hidden sm:flex gap-xl justify-center">
30-
<li>
31-
<a
32-
class="font-semibold flex gap-md items-center p-md bg-linear-to-b rounded-full from-gray-300 to-gray-200 text-black dark:from-charcoal-600 dark:to-charcoal-700 dark:text-white opacity-90 hover:opacity-95 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300"
33-
href=""
34-
>
35-
<BaseIcon name="file-code-2" />
36-
{{ $t('base.products.api') }}
37-
</a>
38-
</li>
39-
<li>
40-
<a
41-
class="font-semibold flex gap-md items-center p-md bg-linear-to-b rounded-full from-gray-300 to-gray-200 text-black dark:from-charcoal-600 dark:to-charcoal-700 dark:text-white opacity-90 hover:opacity-95 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300"
42-
href=""
43-
>
44-
<BaseIcon name="coins" />
45-
{{ $t('base.products.stacking_hub') }}
46-
</a>
47-
</li>
48-
<li>
49-
<a
50-
class="font-semibold flex gap-md items-center p-md bg-linear-to-b rounded-full from-gray-300 to-gray-200 text-black dark:from-charcoal-600 dark:to-charcoal-700 dark:text-white opacity-90 hover:opacity-95 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300"
51-
href=""
52-
>
53-
<BaseIcon name="compass" />
54-
{{ $t('base.products.explorer') }}
55-
</a>
42+
<li
43+
v-for="{ icon, label, to } in items"
44+
:key="label"
45+
>
46+
<BaseNavigationItem
47+
:icon
48+
:label
49+
:to
50+
/>
5651
</li>
5752
</ul>
5853
<BaseButton
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import type { NuxtLinkProps } from '#app'
3+
import type { IconName } from '#layers/base/app/components/BaseIcon.vue'
4+
5+
export type BaseNavigationItem = {
6+
icon: IconName,
7+
label: string,
8+
to: NuxtLinkProps['to'],
9+
}
10+
11+
defineProps<BaseNavigationItem>()
12+
</script>
13+
14+
<template>
15+
<NuxtLink
16+
class="font-semibold flex gap-md items-center p-md bg-linear-to-b rounded-full from-gray-300 to-gray-200 dark:from-charcoal-600 dark:to-charcoal-700 opacity-90 hover:opacity-95 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300"
17+
:to
18+
>
19+
<BaseIcon :name="icon" />
20+
{{ label }}
21+
</NuxtLink>
22+
</template>
23+
24+
<style scoped></style>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script setup lang="ts">
2+
import type { BaseNavigationItem } from '~/layers/base/app/components/BaseNavigationItem.vue'
3+
4+
defineProps<{
5+
isOpen: boolean,
6+
items: BaseNavigationItem[],
7+
}>()
8+
const emit = defineEmits<{
9+
(e: 'close'): void,
10+
}>()
11+
</script>
12+
13+
<template>
14+
<transition
15+
enter-from-class="-translate-x-full sm:-translate-x-full"
16+
enter-active-class="transition transform duration-200 ease-in-out"
17+
leave-to-class="-translate-x-full sm:-translate-x-full"
18+
leave-active-class="transition transform duration-200 ease-in-out"
19+
>
20+
<LazyBaseNavigationLeftContent
21+
v-if="isOpen"
22+
v-bind="$attrs"
23+
:items
24+
:is-open
25+
@close="emit('close')"
26+
/>
27+
</transition>
28+
</template>
29+
30+
<style scoped></style>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<script setup lang="ts">
2+
import { useBreakpoints } from '#layers/base/app/composables/useBreakpoints'
3+
import type { BaseNavigationItem } from '~/layers/base/app/components/BaseNavigationItem.vue'
4+
5+
const navigation = useTemplateRef('navigation')
6+
defineProps<{
7+
isOpen: boolean,
8+
items: BaseNavigationItem[],
9+
}>()
10+
onClickOutside(navigation, () => {
11+
emit('close')
12+
})
13+
14+
const emit = defineEmits<{
15+
(e: 'close'): void,
16+
}>()
17+
18+
const close = () => {
19+
emit('close')
20+
}
21+
const { sm } = useBreakpoints()
22+
23+
const previousActiveElement = ref<Element | null>()
24+
const closeButton = useTemplateRef('closeButton')
25+
onBeforeMount(() => {
26+
previousActiveElement.value = document.activeElement
27+
})
28+
onMounted(() => {
29+
closeButton.value?.$el?.focus()
30+
})
31+
onUnmounted(() => {
32+
if (previousActiveElement.value instanceof HTMLElement) {
33+
previousActiveElement.value.focus()
34+
}
35+
})
36+
37+
watchEffect(() => {
38+
if (sm.value) {
39+
emit('close')
40+
}
41+
})
42+
</script>
43+
44+
<template>
45+
<div
46+
ref="navigation"
47+
:open="isOpen"
48+
class="bg-white dark:bg-black fixed inset-[0] right-auto h-screen p-2xl w-3xs border-r border-r-gray-200 dark:border-r-gray-700"
49+
@keydown.escape="close"
50+
>
51+
<BaseScreenreaderOnly
52+
is="h2"
53+
screenreader-text="base.common.side_navigation"
54+
/>
55+
<section class="flex justify-between">
56+
<span class="flex flex-row gap-sm">
57+
<TheLogoMark
58+
width="1.25rem"
59+
class="aspect-square"
60+
/>
61+
<TheLogoType
62+
width="6.25rem"
63+
/>
64+
</span>
65+
<BaseButtonIcon
66+
ref="closeButton"
67+
screenreader-text="base.common.close"
68+
class=""
69+
name="x"
70+
variant="secondary"
71+
@click="close"
72+
/>
73+
</section>
74+
<nav class="mt-xl">
75+
<ul class="flex flex-col gap-md">
76+
<li
77+
v-for="{ icon, label, to } in items"
78+
:key="label"
79+
>
80+
<BaseNavigationLeftItem
81+
:icon
82+
:label
83+
:to
84+
/>
85+
</li>
86+
</ul>
87+
</nav>
88+
</div>
89+
</template>
90+
91+
<style scoped></style>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script setup lang="ts">
2+
import type { BaseNavigationItem } from '~/layers/base/app/components/BaseNavigationItem.vue'
3+
4+
defineProps<BaseNavigationItem>()
5+
</script>
6+
7+
<template>
8+
<NuxtLink
9+
class="font-semibold flex gap-md items-center p-lg opacity-90 hover:opacity-95 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-300 rounded-xl"
10+
:to
11+
>
12+
<BaseIcon :name="icon" />
13+
{{ label }}
14+
</NuxtLink>
15+
</template>
16+
17+
<style scoped></style>

0 commit comments

Comments
 (0)