This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / welcome / WelcomeCard.svelte
3.1 kB 96 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 4 export const welcomeCard = tv({ 5 slots: { 6 root: "overflow-hidden rounded-sm border border-border-default bg-background-default shadow-xs", 7 header: "overflow-x-auto border-b border-border-default px-6 py-5", 8 // grid so the outgoing and incoming panes share one cell and cross-fade in 9 // place; overflow-hidden is what keeps the swipe inside the card 10 body: "grid overflow-hidden p-6", 11 pane: "col-start-1 row-start-1 min-w-0", 12 footer: "flex items-center justify-between gap-4 border-t border-border-default px-6 py-4" 13 } 14 }); 15 16 export type WelcomeCardVariants = VariantProps<typeof welcomeCard>; 17</script> 18 19<script lang="ts"> 20 import type { Snippet } from "svelte"; 21 import { untrack } from "svelte"; 22 import { cubicIn, cubicOut } from "svelte/easing"; 23 import { prefersReducedMotion } from "svelte/motion"; 24 import { fly } from "svelte/transition"; 25 import ArrowLeft from "$icon/arrow-left"; 26 import ArrowRight from "$icon/arrow-right"; 27 import Check from "$icon/check"; 28 import Button from "$lib/components/ui/Button.svelte"; 29 import Steps from "./Steps.svelte"; 30 import { WELCOME_STEPS, LAST_STEP } from "./steps"; 31 32 interface Props { 33 step: number; 34 /** +1 when advancing, -1 when going back — decides which way the pane slides */ 35 direction: number; 36 /** the appview greens Next once the step has something worth saving */ 37 nextReady?: boolean; 38 onBack: () => void; 39 onNext: () => void; 40 /** rendered per step index, frozen at pane creation so the outgoing pane keeps its own content */ 41 children: Snippet<[number]>; 42 } 43 44 let { step, direction, nextReady = false, onBack, onNext, children }: Props = $props(); 45 46 const slot = welcomeCard(); 47 48 const DISTANCE = 40; 49 const enter = $derived( 50 prefersReducedMotion.current 51 ? { duration: 0 } 52 : { x: direction * DISTANCE, duration: 240, delay: 90, easing: cubicOut } 53 ); 54 const leave = $derived( 55 prefersReducedMotion.current 56 ? { duration: 0 } 57 : { x: direction * -DISTANCE, duration: 150, easing: cubicIn } 58 ); 59 60 const isLast = $derived(step === LAST_STEP); 61</script> 62 63<div class={slot.root()}> 64 <div class={slot.header()}> 65 <Steps items={WELCOME_STEPS} current={step} /> 66 </div> 67 68 <div class={slot.body()}> 69 {#key step} 70 <!-- untrack freezes the index at creation: `children` is a live snippet, so 71 without this the leaving pane would re-render with the new step's content 72 halfway through its own exit transition --> 73 {@const frozen = untrack(() => step)} 74 <div class={slot.pane()} in:fly={enter} out:fly={leave}> 75 {@render children(frozen)} 76 </div> 77 {/key} 78 </div> 79 80 <div class={slot.footer()}> 81 {#if step > 0} 82 <Button variant="ghost" icon={ArrowLeft} onclick={onBack}>Back</Button> 83 {:else} 84 <span></span> 85 {/if} 86 87 <Button 88 variant={nextReady ? "primary" : "default"} 89 icon={isLast ? Check : ArrowRight} 90 iconSide={isLast ? "left" : "right"} 91 onclick={onNext} 92 > 93 {isLast ? "Finish" : "Next"} 94 </Button> 95 </div> 96</div>