mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-21 08:15:12 +02:00
feat: Onboarding
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
30
src/lib/components/SelectField.svelte
Normal file
30
src/lib/components/SelectField.svelte
Normal 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>
|
||||
23
src/lib/components/SelectItem.svelte
Normal file
23
src/lib/components/SelectItem.svelte
Normal 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>
|
||||
27
src/lib/components/Tab/Tab.svelte
Normal file
27
src/lib/components/Tab/Tab.svelte
Normal 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>
|
||||
15
src/lib/components/Tab/Tab.ts
Normal file
15
src/lib/components/Tab/Tab.ts
Normal 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 };
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user