feat: Onboarding

This commit is contained in:
Aleksi Lassila
2024-05-25 00:28:13 +03:00
parent 47845d1dd9
commit dc1b25dc22
22 changed files with 2320 additions and 65 deletions

View File

@@ -39,7 +39,7 @@
<Container
bind:hasFocus
class={classNames(
'h-12 rounded-lg font-medium tracking-wide flex items-center group',
'h-12 rounded-xl font-medium tracking-wide flex items-center group',
{
'bg-secondary-800': type === 'primary',
'bg-primary-900': type === 'primary-dark',
@@ -61,14 +61,14 @@
<div
class={classNames({
contents: type === 'primary' || type === 'primary-dark',
'border-2 border-transparent h-full w-full rounded-md flex items-center px-6':
'border-2 border-transparent h-full w-full rounded-lg flex items-center px-6':
type === 'secondary',
'bg-primary-500 text-secondary-950': type === 'secondary' && $hasFocus,
'group-hover:bg-primary-500 group-hover:text-secondary-950': type === 'secondary',
'!bg-red-500': confirmDanger && armed
})}
>
<div class="flex-1 text-center text-nowrap flex items-center justify-center">
<div class="flex-1 text-center text-nowrap flex items-center justify-center relative">
{#if $$slots.icon}
<div class="mr-2">
<slot name="icon" />
@@ -80,6 +80,11 @@
<slot name="icon-after" />
</div>
{/if}
{#if $$slots['icon-absolute']}
<div class="absolute inset-y-0 right-0 flex items-center justify-center">
<slot name="icon-absolute" />
</div>
{/if}
</div>
</div>
</Container>

View File

@@ -13,7 +13,6 @@
import classNames from 'classnames';
import { type BackEvent, scrollIntoView, Selectable } from '../../selectable';
import { fade } from 'svelte/transition';
import { sonarrService } from '../../stores/sonarr-service.store';
import { createLocalStorageStore } from '../../stores/localstorage.store';
import { formatSize } from '../../utils';
import { capitalize } from '../../utils.js';
@@ -50,7 +49,12 @@
monitorOptions: null
});
$sonarrService.then((s) => {
const sonarrOptions = Promise.all([
sonarrApi.getRootFolders(),
sonarrApi.getQualityProfiles()
]).then(([rootFolders, qualityProfiles]) => ({ rootFolders, qualityProfiles }));
sonarrOptions.then((s) => {
addOptionsStore.update((prev) => ({
rootFolderPath: prev.rootFolderPath || s.rootFolders[0]?.path || null,
qualityProfileId: prev.qualityProfileId || s.qualityProfiles[0]?.id || null,
@@ -108,7 +112,7 @@
/>
{/if}
{#await $sonarrService then { qualityProfiles, rootFolders }}
{#await sonarrOptions then { qualityProfiles, rootFolders }}
{@const selectedRootFolder = rootFolders.find(
(f) => f.path === $addOptionsStore.rootFolderPath
)}

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import Container from '../../Container.svelte';
import { ArrowRight } from 'radix-icons-svelte';
import classNames from 'classnames';
export let value: string;
</script>
<Container
class="flex items-center justify-between bg-primary-900 rounded-xl px-6 py-2.5 mb-4 font-medium
border-2 border-transparent focus:border-primary-500 hover:border-primary-500 cursor-pointer group"
on:clickOrSelect
let:hasFocus
>
<div>
<h1 class="text-secondary-300 font-semibold tracking-wide text-sm">
<slot />
</h1>
<span>
{value}
</span>
</div>
<ArrowRight
class={classNames('transition-transform', {
'text-primary-500 translate-x-0.5 scale-110': hasFocus,
'group-hover:text-primary-500 group-hover:translate-x-0.5 group-hover:scale-110': true
})}
size={24}
/>
</Container>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import Container from '../../Container.svelte';
import { ArrowRight, Check } from 'radix-icons-svelte';
import classNames from 'classnames';
export let selected: boolean;
</script>
<Container
class="flex items-center justify-between bg-primary-900 rounded-xl px-6 py-2.5 mb-4 font-medium
border-2 border-transparent focus:border-primary-500 hover:border-primary-500 cursor-pointer group"
on:clickOrSelect
on:enter
focusOnClick
focusOnMount={selected}
>
<div>
<slot />
</div>
{#if selected}
<Check size={24} />
{/if}
</Container>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import Container from '../../../Container.svelte';
import classNames from 'classnames';
import type { Selectable } from '../../selectable';
export let tab: number;
export let index: number = tab;
export let openTab: number;
let selectable: Selectable;
$: active = tab === openTab;
$: if (active) selectable?.focus();
</script>
<Container
trapFocus
class={classNames($$restProps.class, 'transition-all', {
'opacity-0 pointer-events-none': !active,
'-translate-x-10': !active && openTab >= index,
'translate-x-10': !active && openTab < index
})}
bind:selectable
on:back
>
<slot />
</Container>

View File

@@ -0,0 +1,15 @@
import { writable } from 'svelte/store';
enum TestTabs {
Tab1 = 'Tab1',
Tab2 = 'Tab2',
Tab3 = 'Tab3'
}
const test = useTabs<TestTabs>(TestTabs.Tab1);
export function useTabs<T extends string>(defaultTab: T) {
const tab = writable<string>(defaultTab);
return { subscribe: tab.subscribe };
}

View File

@@ -1,12 +1,29 @@
<script lang="ts">
import Container from '../../Container.svelte';
import type { FormEventHandler, HTMLInputTypeAttribute } from 'svelte/elements';
import { createEventDispatcher } from 'svelte';
import { type ComponentType, createEventDispatcher } from 'svelte';
import { PLATFORM_TV } from '../constants';
import classNames from 'classnames';
import Spinner from './Utils/Spinner.svelte';
import { Check, Cross1 } from 'radix-icons-svelte';
export let value = '';
export let type: HTMLInputTypeAttribute = 'text';
export let isValid: Promise<boolean> | boolean | undefined = undefined;
let icon: ComponentType | undefined = undefined;
$: {
if (isValid instanceof Promise) {
icon = Spinner;
isValid.then((valid) => {
icon = valid ? Check : Cross1;
});
} else if (isValid === false) {
icon = Cross1;
} else if (isValid === true) {
icon = Check;
}
}
const dispatch = createEventDispatcher<{
change: string;
@@ -14,7 +31,9 @@
let input: HTMLInputElement;
const handleChange = (e: Event) => {
// @ts-ignore
value = e.target?.value;
// @ts-ignore
dispatch('change', e.target?.value);
};
</script>
@@ -28,18 +47,26 @@
on:clickOrSelect={() => input?.focus()}
class={classNames('flex flex-col', $$restProps.class)}
let:hasFocus
focusOnClick
>
<label class="text-sm text-zinc-300 mb-1">
<label class="text-secondary-300 font-medium tracking-wide text-sm mb-1">
<slot>Label</slot>
</label>
<input
class={classNames('bg-secondary-800 px-4 py-1.5 rounded-lg', {
selected: hasFocus,
unselected: !hasFocus
})}
{type}
{value}
on:input={handleChange}
bind:this={input}
/>
<div class="relative flex flex-col">
<input
class={classNames('bg-primary-900 px-6 py-2 rounded-lg', {
selected: hasFocus,
unselected: !hasFocus
})}
{type}
{value}
on:input={handleChange}
bind:this={input}
/>
{#if icon}
<div class="absolute inset-y-0 right-4 flex items-center justify-center">
<svelte:component this={icon} size={19} />
</div>
{/if}
</div>
</Container>