improvements to the sleep timer
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 14s
Traceability Validation / Check Requirement Traces (push) Failing after 2s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped

This commit is contained in:
2026-02-28 20:33:22 +01:00
parent e8e37649fa
commit c5be9eb18c
19 changed files with 475 additions and 57 deletions
@@ -0,0 +1,117 @@
<script lang="ts">
interface PickerItem {
value: number | string;
label: string;
}
interface Props {
items: PickerItem[];
selectedValue?: number | string;
visibleCount?: number;
itemHeight?: number;
onSelect?: (value: number | string) => void;
}
let {
items,
selectedValue = items[0]?.value,
visibleCount = 3,
itemHeight = 56,
onSelect,
}: Props = $props();
let scrollContainer: HTMLDivElement | undefined = $state();
let selectedIndex = $state(0);
const paddingCount = $derived(Math.floor(visibleCount / 2));
const containerHeight = $derived(visibleCount * itemHeight);
function handleScroll() {
if (!scrollContainer) return;
const scrollTop = scrollContainer.scrollTop;
const newIndex = Math.round(scrollTop / itemHeight);
if (newIndex >= 0 && newIndex < items.length && newIndex !== selectedIndex) {
selectedIndex = newIndex;
onSelect?.(items[newIndex].value);
}
}
function scrollToIndex(index: number) {
scrollContainer?.scrollTo({
top: index * itemHeight,
behavior: "smooth",
});
}
// Initialize scroll position
$effect(() => {
if (scrollContainer) {
const idx = Math.max(0, items.findIndex((i) => i.value === selectedValue));
scrollContainer.scrollTop = idx * itemHeight;
selectedIndex = idx;
}
});
</script>
<div
class="relative overflow-hidden rounded-lg"
style="height: {containerHeight}px"
>
<!-- Highlight band for center item -->
<div
class="absolute left-0 right-0 pointer-events-none z-10 border-y border-[var(--color-jellyfin)]/40 bg-[var(--color-jellyfin)]/5 rounded"
style="top: {paddingCount * itemHeight}px; height: {itemHeight}px"
></div>
<!-- Fade gradients -->
<div class="absolute top-0 left-0 right-0 h-10 bg-gradient-to-b from-[var(--color-surface)] to-transparent z-20 pointer-events-none"></div>
<div class="absolute bottom-0 left-0 right-0 h-10 bg-gradient-to-t from-[var(--color-surface)] to-transparent z-20 pointer-events-none"></div>
<!-- Scrollable container -->
<div
bind:this={scrollContainer}
onscroll={handleScroll}
class="h-full overflow-y-auto scroll-snap-y scrollbar-none"
style="-webkit-overflow-scrolling: touch"
>
<!-- Top padding -->
{#each Array(paddingCount) as _}
<div style="height: {itemHeight}px"></div>
{/each}
<!-- Items -->
{#each items as item, i}
<button
onclick={() => scrollToIndex(i)}
class="w-full snap-center flex items-center justify-center transition-all duration-150
{i === selectedIndex
? 'text-white text-2xl font-bold'
: 'text-gray-500 text-lg font-medium opacity-60'}"
style="height: {itemHeight}px"
>
{item.label}
</button>
{/each}
<!-- Bottom padding -->
{#each Array(paddingCount) as _}
<div style="height: {itemHeight}px"></div>
{/each}
</div>
</div>
<style>
.scrollbar-none::-webkit-scrollbar {
display: none;
}
.scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scroll-snap-y {
scroll-snap-type: y mandatory;
}
.snap-center {
scroll-snap-align: center;
}
</style>