managarten/packages/shared-ui/src/molecules/ReminderPicker.svelte
Till JS 4fa096147c feat(shared-stores,shared-ui): add shared reminder system
Add notificationService (Browser Notification API wrapper),
createReminderScheduler (30s poller with source pattern for checking
due reminders), and ReminderPicker UI component.

Todo module gets todoReminderSource (checks task dueDate - minutesBefore)
and ReminderSelector now delegates to shared ReminderPicker.

Scheduler supports multiple sources (todo, calendar, planta, etc.),
tag-based dedup, graceful error handling, and runtime source addition.
22 new tests (8 notification + 8 scheduler + 6 ReminderPicker).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:54:15 +02:00

68 lines
1.9 KiB
Svelte

<script lang="ts">
import { Bell, BellSlash } from '@manacore/shared-icons';
/**
* Reusable reminder time picker dropdown.
* Lets user select "X minutes before" for reminders on tasks, events, etc.
*/
interface ReminderOption {
value: number | null;
label: string;
}
interface Props {
/** Selected value in minutes (null = no reminder) */
value: number | null;
/** Called when selection changes */
onChange: (minutes: number | null) => void;
/** Custom options (defaults to standard set) */
options?: ReminderOption[];
/** Disable the picker */
disabled?: boolean;
}
const DEFAULT_OPTIONS: ReminderOption[] = [
{ value: null, label: 'Keine Erinnerung' },
{ value: 5, label: '5 Minuten vorher' },
{ value: 15, label: '15 Minuten vorher' },
{ value: 30, label: '30 Minuten vorher' },
{ value: 60, label: '1 Stunde vorher' },
{ value: 1440, label: '1 Tag vorher' },
];
let { value, onChange, options = DEFAULT_OPTIONS, disabled = false }: Props = $props();
function handleChange(e: Event) {
const target = e.target as HTMLSelectElement;
const raw = target.value;
onChange(raw === '' ? null : parseInt(raw, 10));
}
const displayLabel = $derived(
options.find((o) => o.value === value)?.label ?? 'Keine Erinnerung'
);
const hasReminder = $derived(value !== null);
</script>
<div class="inline-flex items-center gap-1.5">
{#if hasReminder}
<Bell size={14} weight="fill" class="text-primary flex-shrink-0" />
{:else}
<BellSlash size={14} class="text-muted-foreground flex-shrink-0" />
{/if}
<select
class="appearance-none bg-transparent text-xs cursor-pointer
{hasReminder ? 'text-primary font-medium' : 'text-muted-foreground'}
focus:outline-none"
{disabled}
onchange={handleChange}
>
{#each options as option}
<option value={option.value ?? ''} selected={option.value === value}>
{option.label}
</option>
{/each}
</select>
</div>