mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-19 16:53:31 +02:00
330 lines
9.1 KiB
Svelte
330 lines
9.1 KiB
Svelte
<script lang="ts">
|
|
import {
|
|
Bookmark,
|
|
CardStack,
|
|
DotFilled,
|
|
Gear,
|
|
Laptop,
|
|
MagnifyingGlass,
|
|
Person
|
|
} from 'radix-icons-svelte';
|
|
import classNames from 'classnames';
|
|
import { get, type Readable, writable, type Writable } from 'svelte/store';
|
|
import Container from '../../../Container.svelte';
|
|
import { registrars, Selectable } from '../../selectable';
|
|
import { stackRouter, navigate } from '../StackRouter/StackRouter';
|
|
import { onMount } from 'svelte';
|
|
import { useTabs } from '../Tab/Tab';
|
|
import { user } from '../../stores/user.store';
|
|
|
|
enum Tabs {
|
|
Users,
|
|
Series,
|
|
Movies,
|
|
Library,
|
|
Search,
|
|
Manage
|
|
}
|
|
|
|
const tab = useTabs(Tabs.Series);
|
|
|
|
let selectedIndex = 0;
|
|
let activeIndex = -1;
|
|
|
|
// '': 0,
|
|
// series: 0,
|
|
// movies: 1,
|
|
// library: 2,
|
|
// search: 3,
|
|
// manage: 4
|
|
// }[$location.pathname.split('/')[1] || '/'];
|
|
|
|
let isNavBarOpen: Readable<boolean>;
|
|
let focusIndex: Writable<number> = writable(0);
|
|
let selectable: Selectable;
|
|
|
|
focusIndex.subscribe((v) => {
|
|
selectedIndex = v;
|
|
});
|
|
|
|
const selectIndex = (index: number) => () => {
|
|
// if (index === activeIndex) {
|
|
// if (get(selectable.hasFocusWithin)) Selectable.giveFocus('right');
|
|
// }
|
|
selectable.focusChild(index);
|
|
const path =
|
|
{
|
|
[Tabs.Users]: '/users',
|
|
[Tabs.Series]: '/',
|
|
[Tabs.Movies]: '/movies',
|
|
[Tabs.Library]: '/library',
|
|
[Tabs.Search]: '/search',
|
|
[Tabs.Manage]: '/manage'
|
|
}[index] || '/';
|
|
navigate(path);
|
|
selectedIndex = index;
|
|
};
|
|
|
|
onMount(() => {
|
|
// Set active tab based on bottommost page
|
|
stackRouter.subscribe((r) => {
|
|
const bottomPage = r[0];
|
|
if (bottomPage) {
|
|
activeIndex =
|
|
{
|
|
'/users': Tabs.Users,
|
|
'/': Tabs.Series,
|
|
'/series': Tabs.Series,
|
|
'/movies': Tabs.Movies,
|
|
'/library': Tabs.Library,
|
|
'/search': Tabs.Search,
|
|
'/manage': Tabs.Manage
|
|
}[bottomPage.route.path] ?? -1;
|
|
selectable.focusIndex.set(activeIndex);
|
|
selectedIndex = activeIndex;
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<Container
|
|
class={classNames(
|
|
'flex flex-col items-stretch fixed z-20 left-0 inset-y-0 group',
|
|
'py-8 w-24 select-none',
|
|
{
|
|
//'max-w-[64px]': !$isNavBarOpen,
|
|
//'max-w-64': $isNavBarOpen
|
|
}
|
|
)}
|
|
bind:hasFocusWithin={isNavBarOpen}
|
|
bind:focusIndex
|
|
bind:selectable
|
|
on:mount={registrars.sidebar.registrar}
|
|
>
|
|
<!-- Background -->
|
|
<div
|
|
class={classNames(
|
|
'absolute inset-y-0 left-0 w-[25vw] transition-opacity bg-gradient-to-r from-secondary-900 to-transparent',
|
|
{
|
|
'opacity-0': !$isNavBarOpen,
|
|
'group-hover:opacity-100 pointer-events-none': true
|
|
}
|
|
)}
|
|
/>
|
|
<!-- Keep group hovered and sidebar open for width of this -->
|
|
<div
|
|
class={classNames('absolute inset-y-0 left-0 w-48 ', {
|
|
'pointer-events-none': !$isNavBarOpen,
|
|
'group-hover:pointer-events-auto': true
|
|
})}
|
|
/>
|
|
|
|
<Container
|
|
class="w-full h-12 cursor-pointer"
|
|
on:clickOrSelect={selectIndex(Tabs.Users)}
|
|
let:hasFocus
|
|
>
|
|
<div
|
|
class={classNames(
|
|
'w-full h-full relative flex items-center justify-center transition-opacity',
|
|
{
|
|
'text-primary-500': hasFocus || (!$isNavBarOpen && selectedIndex === Tabs.Users),
|
|
'text-stone-300 hover:text-primary-500':
|
|
!hasFocus && !(!$isNavBarOpen && selectedIndex === Tabs.Users),
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
<div class="absolute inset-y-0 left-2 flex items-center justify-center">
|
|
<DotFilled
|
|
class={classNames('text-primary-500', { 'opacity-0': activeIndex !== Tabs.Users })}
|
|
size={19}
|
|
/>
|
|
</div>
|
|
<Person class="w-8 h-8" />
|
|
<span
|
|
class={classNames(
|
|
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
|
{
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
{$user?.name}
|
|
</span>
|
|
</div>
|
|
</Container>
|
|
|
|
<div class={'flex-1 flex flex-col justify-center self-stretch'}>
|
|
<Container
|
|
class="w-full h-12 cursor-pointer"
|
|
on:clickOrSelect={selectIndex(Tabs.Series)}
|
|
let:hasFocus
|
|
focusedChild
|
|
>
|
|
<div
|
|
class={classNames('w-full h-full relative flex items-center justify-center', {
|
|
'text-primary-500': hasFocus || (!$isNavBarOpen && selectedIndex === Tabs.Series),
|
|
'text-stone-300 hover:text-primary-500':
|
|
!hasFocus && !(!$isNavBarOpen && selectedIndex === Tabs.Series)
|
|
})}
|
|
>
|
|
<div class="absolute inset-y-0 left-2 flex items-center justify-center">
|
|
<DotFilled
|
|
class={classNames('text-primary-500', { 'opacity-0': activeIndex !== Tabs.Series })}
|
|
size={19}
|
|
/>
|
|
</div>
|
|
<Laptop class="w-8 h-8" />
|
|
<span
|
|
class={classNames(
|
|
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
|
{
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
Series
|
|
</span>
|
|
</div>
|
|
</Container>
|
|
<Container
|
|
class="w-full h-12 cursor-pointer"
|
|
on:clickOrSelect={selectIndex(Tabs.Movies)}
|
|
let:hasFocus
|
|
>
|
|
<div
|
|
class={classNames('w-full h-full relative flex items-center justify-center', {
|
|
'text-primary-500': hasFocus || (!$isNavBarOpen && selectedIndex === Tabs.Movies),
|
|
'text-stone-300 hover:text-primary-500':
|
|
!hasFocus && !(!$isNavBarOpen && selectedIndex === Tabs.Movies)
|
|
})}
|
|
>
|
|
<div class="absolute inset-y-0 left-2 flex items-center justify-center">
|
|
<DotFilled
|
|
class={classNames('text-primary-500', { 'opacity-0': activeIndex !== Tabs.Movies })}
|
|
size={19}
|
|
/>
|
|
</div>
|
|
<CardStack class="w-8 h-8" />
|
|
<span
|
|
class={classNames(
|
|
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
|
{
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
Movies
|
|
</span>
|
|
</div>
|
|
</Container>
|
|
<Container
|
|
class="w-full h-12 cursor-pointer"
|
|
on:clickOrSelect={selectIndex(Tabs.Library)}
|
|
let:hasFocus
|
|
>
|
|
<div
|
|
class={classNames('w-full h-full relative flex items-center justify-center', {
|
|
'text-primary-500': hasFocus || (!$isNavBarOpen && selectedIndex === Tabs.Library),
|
|
'text-stone-300 hover:text-primary-500':
|
|
!hasFocus && !(!$isNavBarOpen && selectedIndex === Tabs.Library)
|
|
})}
|
|
>
|
|
<div class="absolute inset-y-0 left-2 flex items-center justify-center">
|
|
<DotFilled
|
|
class={classNames('text-primary-500', { 'opacity-0': activeIndex !== Tabs.Library })}
|
|
size={19}
|
|
/>
|
|
</div>
|
|
<Bookmark class="w-8 h-8" />
|
|
<span
|
|
class={classNames(
|
|
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
|
{
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
Library
|
|
</span>
|
|
</div>
|
|
</Container>
|
|
<Container
|
|
class="w-full h-12 cursor-pointer"
|
|
on:clickOrSelect={selectIndex(Tabs.Search)}
|
|
let:hasFocus
|
|
>
|
|
<div
|
|
class={classNames('w-full h-full relative flex items-center justify-center', {
|
|
'text-primary-500': hasFocus || (!$isNavBarOpen && selectedIndex === Tabs.Search),
|
|
'text-stone-300 hover:text-primary-500':
|
|
!hasFocus && !(!$isNavBarOpen && selectedIndex === Tabs.Search)
|
|
})}
|
|
>
|
|
<div class="absolute inset-y-0 left-2 flex items-center justify-center">
|
|
<DotFilled
|
|
class={classNames('text-primary-500', { 'opacity-0': activeIndex !== Tabs.Search })}
|
|
size={19}
|
|
/>
|
|
</div>
|
|
<MagnifyingGlass class="w-8 h-8" />
|
|
<span
|
|
class={classNames(
|
|
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
|
{
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
Search
|
|
</span>
|
|
</div>
|
|
</Container>
|
|
</div>
|
|
|
|
<Container
|
|
class="w-full h-12 cursor-pointer"
|
|
on:clickOrSelect={selectIndex(Tabs.Manage)}
|
|
let:hasFocus
|
|
>
|
|
<div
|
|
class={classNames(
|
|
'w-full h-full relative flex items-center justify-center transition-opacity',
|
|
{
|
|
'text-primary-500': hasFocus || (!$isNavBarOpen && selectedIndex === Tabs.Manage),
|
|
'text-stone-300 hover:text-primary-500':
|
|
!hasFocus && !(!$isNavBarOpen && selectedIndex === Tabs.Manage),
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
<div class="absolute inset-y-0 left-2 flex items-center justify-center">
|
|
<DotFilled
|
|
class={classNames('text-primary-500', { 'opacity-0': activeIndex !== Tabs.Manage })}
|
|
size={19}
|
|
/>
|
|
</div>
|
|
<Gear class="w-8 h-8" />
|
|
<span
|
|
class={classNames(
|
|
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
|
{
|
|
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
|
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
|
}
|
|
)}
|
|
>
|
|
Manage
|
|
</span>
|
|
</div>
|
|
</Container>
|
|
</Container>
|