mirror of
https://github.com/SrIzan10/lofi.git
synced 2026-06-06 00:56:53 +00:00
feat: initial todo list implementation
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
import { getGeneralData, getStationSongs } from '@/utils';
|
||||
import { onMount } from 'svelte';
|
||||
import { useIsMobile } from '@/isMobile.svelte';
|
||||
import Window from '../ui/window/window.svelte';
|
||||
import TodoList from './todo-list.svelte';
|
||||
|
||||
// svelte-ignore non_reactive_update
|
||||
let audioElement: HTMLAudioElement;
|
||||
@@ -233,4 +235,16 @@
|
||||
autoplay
|
||||
preload="none"
|
||||
></audio>
|
||||
{/each}
|
||||
{/each}
|
||||
|
||||
<Window
|
||||
title="Todo List"
|
||||
showTitleBar={true}
|
||||
showCloseButton={true}
|
||||
width={320}
|
||||
height={400}
|
||||
onClose={() => appState.showTodoList = false}
|
||||
show={appState.showTodoList}
|
||||
>
|
||||
<TodoList></TodoList>
|
||||
</Window>
|
||||
13
src/lib/components/app/left-bar.svelte
Normal file
13
src/lib/components/app/left-bar.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { state as appState } from '@/state.svelte';
|
||||
import Check from '@lucide/svelte/icons/check';
|
||||
import Button from '../ui/button/button.svelte';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="absolute left-2 top-1/2 transform -translate-y-1/2 p-4 bg-white/10 backdrop-blur-lg rounded-xl shadow-lg"
|
||||
>
|
||||
<Button size="icon" onclick={() => (appState.showTodoList = !appState.showTodoList)}>
|
||||
<Check class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
50
src/lib/components/app/todo-list.svelte
Normal file
50
src/lib/components/app/todo-list.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { state as appState } from '@/state.svelte'
|
||||
import Button from '../ui/button/button.svelte';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import { useIsMobile } from '@/isMobile.svelte';
|
||||
|
||||
let newTodoText = '';
|
||||
let mobile = useIsMobile();
|
||||
|
||||
function addTodo() {
|
||||
if (newTodoText.trim()) {
|
||||
appState.todoList = [...appState.todoList, newTodoText];
|
||||
newTodoText = '';
|
||||
}
|
||||
}
|
||||
|
||||
function removeTodo(index: number) {
|
||||
appState.todoList = appState.todoList.filter((_, i) => i !== index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 mb-6">
|
||||
<input
|
||||
bind:value={newTodoText}
|
||||
placeholder="Add a new task..."
|
||||
class="flex-1 pl-3 rounded-lg bg-white bg-opacity-10 border border-white border-opacity-20 text-white placeholder-white placeholder-opacity-60 focus:outline-none focus:ring-2 focus:ring-white focus:ring-opacity-40"
|
||||
on:keypress={(e) => e.key === 'Enter' && addTodo()}
|
||||
/>
|
||||
<Button onclick={addTodo} class="bg-white bg-opacity-20 hover:bg-opacity-30 text-white px-4 py-2 rounded-lg" size="icon"><Plus /></Button>
|
||||
</div>
|
||||
|
||||
{#if appState.todoList.length === 0}
|
||||
<p class="text-white text-opacity-70 text-center py-4">No tasks yet. Add one above!</p>
|
||||
{:else}
|
||||
<ul class="space-y-2">
|
||||
{#each appState.todoList as todo, index}
|
||||
<li class="flex items-center justify-between p-3 bg-white bg-opacity-10 rounded-lg group hover:bg-opacity-15 transition-all">
|
||||
<span class="text-white">{todo}</span>
|
||||
<button
|
||||
on:click={() => removeTodo(index)}
|
||||
class="text-white hover:text-red-300 focus:outline-none"
|
||||
aria-label="Remove todo"
|
||||
>
|
||||
<X class="size-5" />
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
@@ -14,6 +14,7 @@
|
||||
showTitleBar = true,
|
||||
showCloseButton = true,
|
||||
onClose = () => {},
|
||||
show = false,
|
||||
initialZIndex = 50,
|
||||
}: {
|
||||
children?: Snippet;
|
||||
@@ -25,6 +26,7 @@
|
||||
showTitleBar?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
onClose?: () => void;
|
||||
show?: boolean;
|
||||
initialZIndex?: number;
|
||||
} = $props();
|
||||
|
||||
@@ -107,37 +109,36 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={windowRef}
|
||||
class="fixed flex flex-col bg-white/10 backdrop-blur-md border border-white/20 shadow-lg rounded-lg overflow-hidden"
|
||||
style="width: {width}px; height: {height}px; left: {x}px; top: {y}px; z-index: {zIndex};"
|
||||
onmousedown={handleMouseDown}
|
||||
role="dialog"
|
||||
tabindex="0"
|
||||
>
|
||||
{#if showTitleBar}
|
||||
<div
|
||||
bind:this={headerRef}
|
||||
class="h-8 px-3 flex items-center justify-between bg-black/10 border-b border-white/10 select-none"
|
||||
style="cursor: {isDragging ? 'grabbing' : 'grab'};"
|
||||
>
|
||||
<span class="text-sm font-medium text-white/90">{title}</span>
|
||||
{#if showCloseButton}
|
||||
<button
|
||||
onclick={onClose}
|
||||
class="w-5 h-5 flex items-center justify-center text-white/70 hover:text-white hover:bg-red-500/50 rounded-sm transition-colors"
|
||||
aria-label="Close window"
|
||||
>
|
||||
<X class="size-4" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if show}
|
||||
<div
|
||||
bind:this={windowRef}
|
||||
class="fixed flex flex-col bg-white/10 backdrop-blur-md border border-white/20 shadow-lg rounded-lg overflow-hidden"
|
||||
style="width: {width}px; height: {height}px; left: {x}px; top: {y}px; z-index: {zIndex};"
|
||||
onmousedown={handleMouseDown}
|
||||
role="dialog"
|
||||
tabindex="0"
|
||||
>
|
||||
{#if showTitleBar}
|
||||
<div
|
||||
bind:this={headerRef}
|
||||
class="h-8 px-3 flex items-center justify-between bg-black/10 border-b border-white/10 select-none"
|
||||
style="cursor: {isDragging ? 'grabbing' : 'grab'};"
|
||||
>
|
||||
<span class="text-sm font-medium text-white/90">{title}</span>
|
||||
{#if showCloseButton}
|
||||
<button
|
||||
onclick={onClose}
|
||||
class="w-5 h-5 flex items-center justify-center text-white/70 hover:text-white hover:bg-red-500/50 rounded-sm transition-colors"
|
||||
aria-label="Close window"
|
||||
>
|
||||
<X class="size-4" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex-1 p-1 overflow-auto bg-transparent" role="dialog" tabindex="0">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex-1 p-1 overflow-auto bg-transparent" role="dialog" tabindex="0">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
{/if}
|
||||
@@ -23,6 +23,9 @@ export const state = $state({
|
||||
|
||||
windowZIndexCounter: 50,
|
||||
|
||||
todoList: [] as string[],
|
||||
showTodoList: false,
|
||||
|
||||
// in daemon.svelte
|
||||
togglePlay: (() => {}) as () => void,
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { state } from '@/state.svelte';
|
||||
import BackgroundAnalyzer from '@/components/app/bg-analyzer.svelte';
|
||||
import Title from '@/components/app/title.svelte';
|
||||
import LeftBar from '@/components/app/left-bar.svelte';
|
||||
</script>
|
||||
|
||||
<BgImage />
|
||||
@@ -28,5 +29,6 @@
|
||||
</div>
|
||||
{:else if state.hasInteracted}
|
||||
<Title />
|
||||
<LeftBar />
|
||||
<BottomBar />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user