From 3b25059fb97bd5edbf9f1ace4194ec2ea4323fff Mon Sep 17 00:00:00 2001 From: eti Date: Tue, 4 Aug 2026 16:53:20 +0200 Subject: [PATCH] web: add the welcome onboarding flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four steps in one card — profile, follow & star, ssh keys, finish — with the same content as the appview's /welcome templates. The step rail sits at the top and the body slides left or right as you move between steps. Data is mocked, nothing talks to an api yet. Field and Textarea gained the variants the steps needed: sentence-case labels, and a shorter box for the ssh key. Signed-off-by: eti --- web/src/lib/components/icons/Bluesky.svelte | 24 +++ web/src/lib/components/ui/Field.svelte | 39 +++- web/src/lib/components/ui/Textarea.svelte | 25 ++- .../lib/components/welcome/FinishStep.svelte | 167 ++++++++++++++++++ .../lib/components/welcome/KeysStep.svelte | 68 +++++++ .../lib/components/welcome/ProfileStep.svelte | 73 ++++++++ .../lib/components/welcome/SocialStep.svelte | 32 ++++ .../lib/components/welcome/StepHeader.svelte | 32 ++++ web/src/lib/components/welcome/Steps.svelte | 79 +++++++++ .../lib/components/welcome/WelcomeCard.svelte | 96 ++++++++++ .../lib/components/welcome/draft.svelte.ts | 33 ++++ web/src/lib/components/welcome/mock.ts | 95 ++++++++++ web/src/lib/components/welcome/steps.ts | 16 ++ web/src/routes/+layout.svelte | 6 +- web/src/routes/welcome/+page.svelte | 69 ++++++++ 15 files changed, 838 insertions(+), 16 deletions(-) create mode 100644 web/src/lib/components/icons/Bluesky.svelte create mode 100644 web/src/lib/components/welcome/FinishStep.svelte create mode 100644 web/src/lib/components/welcome/KeysStep.svelte create mode 100644 web/src/lib/components/welcome/ProfileStep.svelte create mode 100644 web/src/lib/components/welcome/SocialStep.svelte create mode 100644 web/src/lib/components/welcome/StepHeader.svelte create mode 100644 web/src/lib/components/welcome/Steps.svelte create mode 100644 web/src/lib/components/welcome/WelcomeCard.svelte create mode 100644 web/src/lib/components/welcome/draft.svelte.ts create mode 100644 web/src/lib/components/welcome/mock.ts create mode 100644 web/src/lib/components/welcome/steps.ts create mode 100644 web/src/routes/welcome/+page.svelte diff --git a/web/src/lib/components/icons/Bluesky.svelte b/web/src/lib/components/icons/Bluesky.svelte new file mode 100644 index 00000000..72be9924 --- /dev/null +++ b/web/src/lib/components/icons/Bluesky.svelte @@ -0,0 +1,24 @@ + + + + Bluesky + + diff --git a/web/src/lib/components/ui/Field.svelte b/web/src/lib/components/ui/Field.svelte index 0df26037..0eca6620 100644 --- a/web/src/lib/components/ui/Field.svelte +++ b/web/src/lib/components/ui/Field.svelte @@ -1,3 +1,28 @@ + + -
+
{#if label} -
diff --git a/web/src/lib/components/ui/Textarea.svelte b/web/src/lib/components/ui/Textarea.svelte index f320f907..ca85aa78 100644 --- a/web/src/lib/components/ui/Textarea.svelte +++ b/web/src/lib/components/ui/Textarea.svelte @@ -9,14 +9,20 @@ export const textareaField = tv({ slots: { root: "relative flex items-start gap-1 rounded-sm border bg-background-default transition-colors", - // the floor has to live on the textarea, not the root: the resize handle only ever - // shrinks the textarea, so a min-height on the root would let the handle drag past the - // box's own minimum and leave dead space under the field. keep this in sync with - // textareaMinHeight above — it's 2px shorter because the root's border sits outside it. textarea: - "min-h-63.5 flex-1 self-stretch bg-transparent py-2 typography-paragraph-small leading-6 outline-none placeholder:text-foreground-placeholder read-only:resize-none disabled:cursor-not-allowed disabled:resize-none" + "flex-1 self-stretch bg-transparent py-2 outline-none placeholder:text-foreground-placeholder read-only:resize-none disabled:cursor-not-allowed disabled:resize-none" }, variants: { + // the floor has to live on the textarea, not the root: the resize handle only ever + // shrinks the textarea, so a min-height on the root would let the handle drag past the + // box's own minimum and leave dead space under the field. `default` keeps + // textareaMinHeight above in sync — it's 2px shorter because the root's border sits + // outside it. `compact` is for short fields (an ssh key, a one-line note) where the + // editor-sized floor would swallow the form. + size: { + default: { textarea: "min-h-63.5" }, + compact: { textarea: "min-h-20" } + }, error: { false: { root: "border-border-default focus-within:border-border-strong focus-within:ring-1 focus-within:ring-border-strong" @@ -46,7 +52,8 @@ defaultVariants: { error: false, disabled: false, - readonly: false + readonly: false, + size: "default" } }); @@ -65,6 +72,7 @@ loading?: boolean; readonly?: boolean; resizeable?: boolean; + size?: TextareaFieldVariants["size"]; iconLeft?: Component; iconRight?: Component; class?: string; @@ -78,6 +86,7 @@ loading = false, readonly = false, resizeable = false, + size = "default", iconLeft, iconRight, class: className, @@ -85,7 +94,9 @@ ...rest }: Props = $props(); - const { root, textarea } = $derived(textareaField({ error, disabled, readonly, resizeable })); + const { root, textarea } = $derived( + textareaField({ error, disabled, readonly, resizeable, size }) + ); // The root no longer carries the text inset (so the scrollbar/resize handle can sit flush // against the border) — the textarea itself owns its horizontal padding, collapsing to 0 on diff --git a/web/src/lib/components/welcome/FinishStep.svelte b/web/src/lib/components/welcome/FinishStep.svelte new file mode 100644 index 00000000..904a900c --- /dev/null +++ b/web/src/lib/components/welcome/FinishStep.svelte @@ -0,0 +1,167 @@ + + + + +
+ + +
+
+ {@render heading(BookMarked, "Create a repository")} +

Kick things off by creating your first repository.

+ + Create + +
+ +
+ {@render heading(GitPullRequest, "Contribute to the Tangled project")} +

+ Tangled is fully open-source: Open a feature request, or submit a bug-fix! +

+ All contributions are welcome. +

+ + Contribute + +
+ + {#each docs as doc (doc.title)} +
+ {@render heading(doc.icon, doc.title)} +

{doc.body}

+ + Read the docs + +
+ {/each} +
+ + +
+ +{#snippet heading(icon: Component, title: string)} + {@const Icon = icon} + + +{/snippet} diff --git a/web/src/lib/components/welcome/KeysStep.svelte b/web/src/lib/components/welcome/KeysStep.svelte new file mode 100644 index 00000000..eff4fa66 --- /dev/null +++ b/web/src/lib/components/welcome/KeysStep.svelte @@ -0,0 +1,68 @@ + + +
+ + + {#if draft.keys.length > 0} +
    + {#each draft.keys as pubkey (pubkey.id)} +
  • + removeKey(pubkey.id)} + /> +
  • + {/each} +
+ {/if} + +
+ + {#snippet children({ id })} + + {/snippet} + + + + {#snippet children({ id })} +