Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Svelte
at icy/tmrrpn
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102<script module lang="ts"> import { tv, type VariantProps } from "tailwind-variants";
export const avatarPicker = tv({ slots: { label: "flex size-40 cursor-pointer flex-col items-center justify-center gap-1 overflow-hidden rounded-full border border-border-default text-foreground-subtle transition-colors hover:bg-background-subtle", image: "size-full rounded-full object-cover", placeholder: "flex flex-col items-center justify-center gap-1" } });
export type AvatarPickerVariants = VariantProps<typeof avatarPicker>;
export interface ProfileDraft { description: string; pronouns: string; website: string; avatarFile: File | null; avatarPreview?: string; }</script>
<script lang="ts"> import Camera from "$icon/camera"; import Field from "$lib/components/ui/Field.svelte"; import Input from "$lib/components/ui/Input.svelte"; import StepHeader from "./StepHeader.svelte";
let { draft }: { draft: ProfileDraft } = $props();
const slot = avatarPicker(); const acceptedTypes: Record<string, true> = { "image/png": true, "image/jpeg": true }; let pictureError = $state<string>();
const pick = (event: Event) => { const file = (event.currentTarget as HTMLInputElement).files?.[0]; if (!file) return; if (!acceptedTypes[file.type]) { pictureError = "Choose a PNG or JPEG image."; return; } if (file.size > 1_000_000) { pictureError = "Profile pictures must be 1 MB or smaller."; return; } if (draft.avatarPreview) URL.revokeObjectURL(draft.avatarPreview); draft.avatarFile = file; draft.avatarPreview = URL.createObjectURL(file); pictureError = undefined; };</script>
<div class="flex flex-col gap-6"> <StepHeader title="Welcome!" subtitle="Let's get your profile ready. All fields are optional." />
<div class="flex flex-col gap-6 md:flex-row"> <div class="flex shrink-0 justify-center md:block"> <label class={slot.label()}> {#if draft.avatarPreview} <img src={draft.avatarPreview} alt="Your new avatar" class={slot.image()} /> {:else} <span class={slot.placeholder()}> <Camera class="size-6" aria-hidden="true" /> <span class="typography-paragraph-small">Upload picture</span> </span> {/if} <input type="file" accept="image/png,image/jpeg" class="hidden" onchange={pick} /> </label> {#if pictureError} <p class="mt-2 typography-paragraph-small text-foreground-danger">{pictureError}</p> {/if} </div>
<div class="flex min-w-0 flex-1 flex-col gap-4"> <Field label="Bio" labelCase="normal"> {#snippet children({ id })} <Input {id} placeholder="Tell us about yourself" bind:value={draft.description} /> {/snippet} </Field>
<Field label="Pronouns" labelCase="normal"> {#snippet children({ id })} <Input {id} placeholder="they/them" bind:value={draft.pronouns} /> {/snippet} </Field>
<Field label="Website" labelCase="normal"> {#snippet children({ id })} <Input {id} placeholder="example.com" bind:value={draft.website} /> {/snippet} </Field> </div> </div></div>