feat(planner): add MealActionSheet component for mobile swap trigger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
111
frontend/src/lib/planner/MealActionSheet.svelte
Normal file
111
frontend/src/lib/planner/MealActionSheet.svelte
Normal file
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
interface SlotRecipe {
|
||||
id: string;
|
||||
name: string;
|
||||
effort?: string;
|
||||
cookTimeMin?: number;
|
||||
}
|
||||
|
||||
interface Slot {
|
||||
id?: string;
|
||||
slotDate?: string;
|
||||
recipe: SlotRecipe | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
slot: Slot;
|
||||
onswap: () => void;
|
||||
oncancel: () => void;
|
||||
}
|
||||
|
||||
let { open, slot, onswap, oncancel }: Props = $props();
|
||||
|
||||
const meta = $derived.by(() => {
|
||||
const parts: string[] = [];
|
||||
if (slot.recipe?.cookTimeMin != null) parts.push(`${slot.recipe.cookTimeMin} min`);
|
||||
if (slot.recipe?.effort) parts.push(slot.recipe.effort);
|
||||
return parts.join(' · ');
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!open) return;
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') oncancel();
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
return () => window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="presentation"
|
||||
data-testid="sheet-backdrop"
|
||||
style="position:fixed;inset:0;z-index:50;background:rgba(28,28,24,0.4)"
|
||||
onclick={oncancel}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
style="position:absolute;bottom:0;left:0;right:0;background:var(--color-page);border-radius:var(--radius-xl) var(--radius-xl) 0 0;box-shadow:var(--shadow-overlay)"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<!-- Drag handle -->
|
||||
<div style="display:flex;justify-content:center;margin-top:12px">
|
||||
<div style="width:32px;height:4px;background:var(--color-border);border-radius:9999px"></div>
|
||||
</div>
|
||||
|
||||
<!-- Meal title -->
|
||||
<p style="font-family:var(--font-display);font-size:15px;font-weight:500;color:var(--color-text);padding:0 16px;margin:12px 0 4px">
|
||||
{slot.recipe?.name ?? ''}
|
||||
</p>
|
||||
|
||||
<!-- Metadata -->
|
||||
{#if meta}
|
||||
<p style="font-family:var(--font-sans);font-size:11px;color:var(--color-text-muted);padding:0 16px 12px;margin:0">
|
||||
{meta}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="padding:0 16px 16px;display:flex;flex-direction:column;gap:6px">
|
||||
<button
|
||||
type="button"
|
||||
style="width:100%;background:var(--orange-tint);border:1px solid #FBCDA4;color:var(--orange-dark);font-family:var(--font-sans);font-size:13px;font-weight:500;border-radius:var(--radius-lg);padding:12px;text-align:center;cursor:pointer"
|
||||
onclick={onswap}
|
||||
>
|
||||
↻ Swap this meal
|
||||
</button>
|
||||
|
||||
{#if slot.recipe}
|
||||
<a
|
||||
href="/recipes/{slot.recipe.id}/cook"
|
||||
style="display:block;width:100%;background:var(--green-tint);border:1px solid var(--green-light);color:var(--green-dark);font-family:var(--font-sans);font-size:13px;font-weight:500;border-radius:var(--radius-lg);padding:12px;text-align:center;box-sizing:border-box;text-decoration:none"
|
||||
>
|
||||
🍳 Cook now
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/recipes/{slot.recipe.id}"
|
||||
style="display:block;width:100%;background:var(--color-subtle);border:1px solid var(--color-border);color:var(--color-text-muted);font-family:var(--font-sans);font-size:13px;font-weight:500;border-radius:var(--radius-lg);padding:12px;text-align:center;box-sizing:border-box;text-decoration:none"
|
||||
>
|
||||
👁 View recipe
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
style="width:100%;background:none;border:none;color:var(--color-text-muted);font-family:var(--font-sans);font-size:13px;text-align:center;cursor:pointer;padding:12px"
|
||||
onclick={oncancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user