Skip to content

Commit 62d84f7

Browse files
committed
feat(navbar): adds account dropdown tab
1 parent c09c272 commit 62d84f7

15 files changed

+251
-69
lines changed

src/components/AppComponent.vue

+5-7
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import { Transition } from 'vue';
33
import { RouterView } from 'vue-router';
44
55
import { NavbarComponent } from '~/components/common';
6-
import { useSettingsStoreRefs } from '~/stores/settings.store';
7-
import { useI18n } from '~/utils';
6+
import { useAuthSettingsStoreRefs } from '~/stores/settings.store';
87
9-
const i18n = useI18n('global');
10-
const { isAuthenticated } = useSettingsStoreRefs();
8+
const { isAuthenticated } = useAuthSettingsStoreRefs();
119
</script>
1210

1311
<template>
@@ -33,13 +31,13 @@ header {
3331
display: flex;
3432
flex-direction: column;
3533
justify-content: center;
36-
background: rgb(0 0 0 / 30%);
37-
backdrop-filter: blur(2px);
34+
background: var(--bg-blur-black);
35+
backdrop-filter: blur(var(--bg-blur));
3836
transition: background 0.5s;
3937
will-change: background;
4038
4139
&:hover {
42-
background: rgb(0 0 0 / 60%);
40+
background: var(--bg-blur-black-hover);
4341
}
4442
}
4543

src/components/common/navbar/NavbarComponent.vue

+45-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<script lang="ts" setup>
22
import { NTab, NTabs } from 'naive-ui';
3+
import { ref } from 'vue';
34
import { useRoute, useRouter } from 'vue-router';
45
6+
import NavbarSettingsDropdown from '~/components/common/navbar/NavbarSettingsDopdown.vue';
7+
58
import { Route } from '~/router';
69
import { useI18n } from '~/utils';
710
@@ -13,17 +16,39 @@ const navigate = (to: Route) => {
1316
router.push(to);
1417
};
1518
16-
const routes = [Route.Progress, Route.Calendar, Route.History, Route.List, Route.Search, Route.Settings];
19+
const routes = [Route.Progress, Route.Calendar, Route.History, Route.List, Route.Search];
20+
21+
const navElement = ref<HTMLElement>();
1722
</script>
1823

1924
<template>
20-
<nav>
21-
<NTabs :value="route.name?.toString()" class="tabs" type="segment" justify-content="space-evenly" animated>
25+
<nav ref="navElement">
26+
<NTabs
27+
:value="route.name?.toString()"
28+
class="tabs"
29+
type="segment"
30+
justify-content="space-evenly"
31+
animated
32+
>
2233
<template v-for="_route in routes" :key="_route">
23-
<NTab class="tab" :name="_route.toLowerCase()" type="segment" @click="navigate(_route)">
24-
{{ i18n(_route.toLowerCase()) }}
34+
<NTab
35+
class="tab"
36+
:name="_route.toLowerCase()"
37+
type="segment"
38+
@click="navigate(_route)"
39+
>
40+
<span> {{ i18n(_route.toLowerCase()) }}</span>
2541
</NTab>
2642
</template>
43+
<NTab
44+
class="tab"
45+
style="position: relative"
46+
:name="Route.Settings.toLowerCase()"
47+
type="segment"
48+
@click="navigate(Route.Settings)"
49+
>
50+
<NavbarSettingsDropdown v-if="navElement" :parent-element="navElement" />
51+
</NTab>
2752
</NTabs>
2853
</nav>
2954
</template>
@@ -52,13 +77,25 @@ nav {
5277
.tabs {
5378
--n-bar-color: var(--trakt-red-dark) !important;
5479
--n-tab-text-color-active: var(--vt-c-white) !important;
55-
--n-tab-text-color-hover: color-mix(in srgb, var(--trakt-red) 90%, var(--vt-c-white)) !important;
56-
--n-tab-color-segment: color-mix(in srgb, var(--trakt-red) 50%, transparent) !important;
80+
--n-tab-text-color-hover: color-mix(
81+
in srgb,
82+
var(--trakt-red) 90%,
83+
var(--vt-c-white)
84+
) !important;
85+
--n-tab-color-segment: color-mix(
86+
in srgb,
87+
var(--trakt-red) 50%,
88+
transparent
89+
) !important;
5790
--n-color-segment: inherit !important;
5891
5992
/* stylelint-disable-next-line selector-class-pattern -- overriding theme class */
6093
.n-tabs-tab--active {
61-
--n-tab-text-color-hover: color-mix(in srgb, var(--trakt-white) 99%, var(--vt-c-red)) !important;
94+
--n-tab-text-color-hover: color-mix(
95+
in srgb,
96+
var(--trakt-white) 99%,
97+
var(--vt-c-red)
98+
) !important;
6299
}
63100
64101
.n-tabs-capsule {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<script setup lang="ts">
2+
import { NAvatar, NDropdown, NEllipsis, NIcon, NSpace } from 'naive-ui';
3+
import { computed, defineProps, h } from 'vue';
4+
5+
import IconAccount from '~/components/icons/IconAccount.vue';
6+
import IconLogOut from '~/components/icons/IconLogOut.vue';
7+
8+
import { Route } from '~/router';
9+
import { useUserSettingsStoreRefs } from '~/stores/settings.store';
10+
import { Blurs, Colors } from '~/styles/colors.style';
11+
import { useI18n } from '~/utils';
12+
13+
const i18n = useI18n('route');
14+
15+
const { userSetting } = useUserSettingsStoreRefs();
16+
17+
const avatar = computed(() => userSetting.value?.user?.images?.avatar?.full);
18+
const username = computed(() => userSetting.value?.user?.username);
19+
20+
defineProps({
21+
parentElement: {
22+
type: HTMLElement,
23+
required: true,
24+
},
25+
});
26+
27+
const options = [
28+
{
29+
label: 'Logout',
30+
key: 'logout',
31+
icon: () =>
32+
h(NIcon, null, {
33+
default: () => h(IconLogOut, { size: '1.5em' }),
34+
}),
35+
},
36+
];
37+
</script>
38+
39+
<template>
40+
<NDropdown
41+
trigger="hover"
42+
:options="options"
43+
:to="parentElement"
44+
placement="bottom-end"
45+
size="small"
46+
class="dropdown"
47+
:style="{
48+
'margin-top': '0.75rem',
49+
'margin-right': '-0.25rem',
50+
'text-align': 'left',
51+
'min-width': 'calc(100vw / 6 - 3px)',
52+
'background-color': Colors.bgBlurBlackHover,
53+
'backdrop-filter': Blurs.blur,
54+
}"
55+
@select="(...args: any[]) => console.info(args)"
56+
>
57+
<NSpace justify="space-around" align="center" :wrap="false">
58+
<NEllipsis
59+
style="
60+
max-width: calc(100vw / 6 - 0.25rem - 3px - 1.75rem);
61+
margin-right: 1.75rem;
62+
margin-left: 0.5rem;
63+
"
64+
:tooltip="{
65+
to: parentElement,
66+
placement: 'left',
67+
delay: 1000,
68+
}"
69+
>
70+
{{ username ?? i18n(Route.Settings.toLowerCase()) }}
71+
</NEllipsis>
72+
73+
<NAvatar
74+
v-if="avatar"
75+
:src="avatar"
76+
round
77+
size="small"
78+
color="transparent"
79+
style="position: absolute; top: 3px; right: 0.5rem"
80+
/>
81+
<NIcon v-else style="position: absolute; top: 0.4rem; right: 0.75rem" size="1.5em">
82+
<IconAccount />
83+
</NIcon>
84+
</NSpace>
85+
</NDropdown>
86+
</template>

src/components/icons/IconAccount.vue

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<template>
2+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
3+
<g
4+
fill="none"
5+
stroke="currentColor"
6+
stroke-dasharray="28"
7+
stroke-dashoffset="28"
8+
stroke-linecap="round"
9+
stroke-width="2"
10+
>
11+
<path d="M4 21V20C4 16.6863 6.68629 14 10 14H14C17.3137 14 20 16.6863 20 20V21">
12+
<animate
13+
fill="freeze"
14+
attributeName="stroke-dashoffset"
15+
dur="0.4s"
16+
values="28;0"
17+
/>
18+
</path>
19+
<path
20+
d="M12 11C9.79086 11 8 9.20914 8 7C8 4.79086 9.79086 3 12 3C14.2091 3 16 4.79086 16 7C16 9.20914 14.2091 11 12 11Z"
21+
>
22+
<animate
23+
fill="freeze"
24+
attributeName="stroke-dashoffset"
25+
begin="0.5s"
26+
dur="0.4s"
27+
values="28;0"
28+
/>
29+
</path>
30+
</g>
31+
</svg>
32+
</template>

src/components/icons/IconCommunity.vue

-7
This file was deleted.

src/components/icons/IconDocumentation.vue

-7
This file was deleted.

src/components/icons/IconEcosystem.vue

-7
This file was deleted.

src/components/icons/IconLogOut.vue

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
3+
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2">
4+
<path
5+
stroke-dasharray="46"
6+
stroke-dashoffset="46"
7+
d="M16 5V4C16 3.44772 15.5523 3 15 3H6C5.44771 3 5 3.44772 5 4V20C5 20.5523 5.44772 21 6 21H15C15.5523 21 16 20.5523 16 20V19"
8+
>
9+
<animate
10+
fill="freeze"
11+
attributeName="stroke-dashoffset"
12+
dur="0.5s"
13+
values="46;0"
14+
/>
15+
</path>
16+
<path stroke-dasharray="12" stroke-dashoffset="12" d="M10 12h11" opacity="0">
17+
<set attributeName="opacity" begin="0.6s" to="1" />
18+
<animate
19+
fill="freeze"
20+
attributeName="stroke-dashoffset"
21+
begin="0.6s"
22+
dur="0.2s"
23+
values="12;0"
24+
/>
25+
</path>
26+
<path
27+
stroke-dasharray="6"
28+
stroke-dashoffset="6"
29+
d="M21 12l-3.5 -3.5M21 12l-3.5 3.5"
30+
opacity="0"
31+
>
32+
<set attributeName="opacity" begin="0.8s" to="1" />
33+
<animate
34+
fill="freeze"
35+
attributeName="stroke-dashoffset"
36+
begin="0.8s"
37+
dur="0.2s"
38+
values="6;0"
39+
/>
40+
</path>
41+
</g>
42+
</svg>
43+
</template>

src/components/icons/IconSupport.vue

-7
This file was deleted.

src/components/icons/IconTooling.vue

-19
This file was deleted.

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ export type { TraktExtension, DefineComponent, WebComponents };
1818
declare global {
1919
interface Window {
2020
chrome: typeof chrome;
21+
trakt: Record<string, unknown>;
2122
}
2223
}

src/styles/base.css

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
--vt-c-text-light-2: rgb(60 60 60 / 66%);
1616
--vt-c-text-dark-1: var(--vt-c-white);
1717
--vt-c-text-dark-2: rgb(235 235 235 / 64%);
18+
19+
/* ui color variables */
20+
--bg-blue: 2px;
21+
--bg-blur-black: rgb(0 0 0 / 30%);
22+
--bg-blur-black-hover: rgb(0 0 0 / 60%);
1823

1924
/* trakt.tv brand color */
2025
--trakt-red: #ed1c24;

src/styles/colors.style.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const Colors = {
2+
bgBlurBlack: 'rgba(0, 0, 0, 30%)',
3+
bgBlurBlackHover: 'rgba(0, 0, 0, 60%)',
4+
} as const;
5+
6+
export const Blurs = {
7+
blur: 'blur(2px)',
8+
} as const;

src/utils/browser/browser-storage.utils.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,33 @@ export const sessionStorage: chrome.storage.StorageArea = chrome?.storage?.sessi
1616
/**
1717
* This function is used to wrap the storage areas to provide type inference and a more convenient interface.
1818
* @param area The storage area to wrap.
19+
* @param name The name of the storage area.
1920
*/
20-
export const storageWrapper = (area: chrome.storage.StorageArea) => ({
21-
get: <T>(key: string): Promise<T> => area.get(key).then(({ [key]: value }) => value),
22-
set: <T>(key: string, value: T): Promise<void> => area.set({ [key]: value }),
23-
remove: (key: string): Promise<void> => area.remove(key),
24-
clear: (): Promise<void> => area.clear(),
25-
});
21+
export const storageWrapper = (area: chrome.storage.StorageArea, name = crypto.randomUUID()) => {
22+
if (!chrome?.storage) {
23+
console.warn('Storage API is not available, using in-memory storage instead.');
24+
const storage: Record<string, unknown> = {};
25+
window.trakt = { ...window.trakt, [name]: storage };
26+
return {
27+
get: async <T>(key: string): Promise<T> => storage[key] as T,
28+
set: async <T>(key: string, value: T): Promise<void> => {
29+
storage[key] = value;
30+
},
31+
remove: async (key: string): Promise<void> => {
32+
delete storage[key];
33+
},
34+
clear: async (): Promise<void> => {
35+
Object.keys(storage).forEach(key => delete storage[key]);
36+
},
37+
};
38+
}
39+
return {
40+
get: <T>(key: string): Promise<T> => area.get(key).then(({ [key]: value }) => value),
41+
set: <T>(key: string, value: T): Promise<void> => area.set({ [key]: value }),
42+
remove: (key: string): Promise<void> => area.remove(key),
43+
clear: (): Promise<void> => area.clear(),
44+
};
45+
};
2646

2747
/**
2848
* This object is used to access the storage areas.

0 commit comments

Comments
 (0)