Sign-up form
'use client';
import { useState, type FormEvent } from 'react';import { Button, Card, Checkbox, Field, Spinner, Stack, Text, TextInput } from '@helix-ui/core';
export interface SignUpFormProps { onSubmit: (creds: { email: string; password: string }) => Promise<void>;}
export function SignUpForm({ onSubmit }: SignUpFormProps) { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirm, setConfirm] = useState(''); const [agree, setAgree] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState<string | null>(null);
const passwordsMatch = password.length >= 8 && password === confirm; const canSubmit = email && passwordsMatch && agree && !busy;
const submit = async (e: FormEvent) => { e.preventDefault(); setError(null); if (!passwordsMatch) { setError("Passwords don't match."); return; } setBusy(true); try { await onSubmit({ email, password }); } catch (err) { setError(err instanceof Error ? err.message : 'Sign-up failed.'); } finally { setBusy(false); } };
return ( <Card variant="elevated" style={{ padding: 'var(--helix-ui-space-8)', maxWidth: 400 }}> <form onSubmit={submit}> <Stack gap={4}> <Text size="2xl" weight="semibold"> Create an account </Text> <Field> <Field.Label>Email</Field.Label> <TextInput type="email" autoComplete="email" value={email} onChange={setEmail} isRequired /> </Field> <Field> <Field.Label>Password</Field.Label> <Field.Description>At least 8 characters.</Field.Description> <TextInput type="password" autoComplete="new-password" value={password} onChange={setPassword} isRequired minLength={8} /> </Field> <Field> <Field.Label>Confirm password</Field.Label> <TextInput type="password" autoComplete="new-password" value={confirm} onChange={setConfirm} isRequired /> </Field> <Checkbox isSelected={agree} onChange={setAgree}> I agree to the <a href="/terms">terms</a> and <a href="/privacy">privacy policy</a>. </Checkbox> {error ? ( <Text size="sm" tone="danger"> {error} </Text> ) : null} <Button type="submit" disabled={!canSubmit || busy}> {busy ? <Spinner size="sm" /> : null} Create account </Button> </Stack> </form> </Card> );}The “disabled until everything’s valid” UX matches Apple’s HIG and
keeps you out of the “user pressed submit and nothing happened”
failure mode. The canSubmit check is reactive — flip any field and
the button enables.