Form
Form は、Webサイトやアプリケーションでユーザー入力を受け取り、送信するためのコンポーネントです。
メールアドレスやパスワードなどの入力欄をまとめ、バリデーション(required / pattern など)や送信処理を一元管理します。
form/login is coming soon.
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Card from '$lib/components/ui/atoms/Card.svelte';
import Checkbox from '$lib/components/ui/atoms/Checkbox.svelte';
import Input from '$lib/components/ui/atoms/Input.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import { Eye, EyeOff } from '@lucide/svelte';
let uid = $props.id();
// 新規登録画面へのURL
let signUpUrl = '#';
// ログイン情報
const formValues = $state({
email: '',
password: '',
checked: false,
});
// パスワードを表示するかどうか
let showPassword = $state(false);
/**
* ログイン情報を送信する
* @param e イベント
*/
function onSubmit(e: Event) {
e.preventDefault();
alert(JSON.stringify({ formValues }));
}
</script>
<div class="flex flex-col justify-center size-full min-h-screen p-6 bg-base-surface-muted overflow-y-auto">
<Card class="max-w-sm w-full bg-base-surface-default mx-auto max-lg:max-w-none">
<form class="flex flex-col gap-4" onsubmit={onSubmit}>
<div>
<h2 class="font-semibold text-base-foreground-default text-lg/tight mb-1.5">ログイン</h2>
<p class="text-base-foreground-muted text-sm/normal">続けるには、ログインしてください。</p>
</div>
<div>
<Label class="mb-2 cursor-pointer" for="email-{uid}">メールアドレス</Label>
<Input id="email-{uid}" type="email" placeholder="sample@example.jp" required bind:value={formValues.email} />
</div>
<div>
<Label class="mb-1.5 cursor-pointer" for="password-{uid}">パスワード</Label>
<p class="text-base-foreground-muted text-sm mb-2">半角英数字を含む8文字以上で入力してください。</p>
<Input
id="password-{uid}"
type={showPassword ? 'text' : 'password'}
placeholder="Password123"
required
bind:value={formValues.password}
>
{#snippet endContent()}
<button type="button" class="cursor-pointer" onclick={() => (showPassword = !showPassword)}>
{#if showPassword}
<Eye class="text-base-foreground-muted" size="1rem" />
{:else}
<EyeOff class="text-base-foreground-muted" size="1rem" />
{/if}
</button>
{/snippet}
</Input>
</div>
<div>
<Label class="!flex items-center gap-2 group cursor-pointer">
<Checkbox bind:checked={formValues.checked} />
<span class="font-normal text-base-foreground-default text-sm/tight">ログイン状態を維持する</span>
</Label>
</div>
<Button type="submit" size="small">ログイン</Button>
<p class="text-center text-base-foreground-muted text-sm/normal">
はじめてご利用の方は<a class="text-base-foreground-link duration-200 ease-in-out underline underline-offset-4 mx-0.5 hover:text-base-foreground-link/90 hover:decoration-transparent" href={signUpUrl}>新規登録</a>へ
</p>
</form>
</Card>
</div>
依存コンポーネント
Formを使うときは、以下のコンポーネントもダウンロードが必要です。
サンプル
Login
ユーザーがアカウントにサインインするためのページです。
form/login is coming soon.
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Card from '$lib/components/ui/atoms/Card.svelte';
import Checkbox from '$lib/components/ui/atoms/Checkbox.svelte';
import Input from '$lib/components/ui/atoms/Input.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import { Eye, EyeOff } from '@lucide/svelte';
let uid = $props.id();
// 新規登録画面へのURL
let signUpUrl = '#';
// ログイン情報
const formValues = $state({
email: '',
password: '',
checked: false,
});
// パスワードを表示するかどうか
let showPassword = $state(false);
/**
* ログイン情報を送信する
* @param e イベント
*/
function onSubmit(e: Event) {
e.preventDefault();
alert(JSON.stringify({ formValues }));
}
</script>
<div class="flex flex-col justify-center size-full min-h-screen p-6 bg-base-surface-muted overflow-y-auto">
<Card class="max-w-sm w-full bg-base-surface-default mx-auto max-lg:max-w-none">
<form class="flex flex-col gap-4" onsubmit={onSubmit}>
<div>
<h2 class="font-semibold text-base-foreground-default text-lg/tight mb-1.5">ログイン</h2>
<p class="text-base-foreground-muted text-sm/normal">続けるには、ログインしてください。</p>
</div>
<div>
<Label class="mb-2 cursor-pointer" for="email-{uid}">メールアドレス</Label>
<Input id="email-{uid}" type="email" placeholder="sample@example.jp" required bind:value={formValues.email} />
</div>
<div>
<Label class="mb-1.5 cursor-pointer" for="password-{uid}">パスワード</Label>
<p class="text-base-foreground-muted text-sm mb-2">半角英数字を含む8文字以上で入力してください。</p>
<Input
id="password-{uid}"
type={showPassword ? 'text' : 'password'}
placeholder="Password123"
required
bind:value={formValues.password}
>
{#snippet endContent()}
<button type="button" class="cursor-pointer" onclick={() => (showPassword = !showPassword)}>
{#if showPassword}
<Eye class="text-base-foreground-muted" size="1rem" />
{:else}
<EyeOff class="text-base-foreground-muted" size="1rem" />
{/if}
</button>
{/snippet}
</Input>
</div>
<div>
<Label class="!flex items-center gap-2 group cursor-pointer">
<Checkbox bind:checked={formValues.checked} />
<span class="font-normal text-base-foreground-default text-sm/tight">ログイン状態を維持する</span>
</Label>
</div>
<Button type="submit" size="small">ログイン</Button>
<p class="text-center text-base-foreground-muted text-sm/normal">
はじめてご利用の方は<a class="text-base-foreground-link duration-200 ease-in-out underline underline-offset-4 mx-0.5 hover:text-base-foreground-link/90 hover:decoration-transparent" href={signUpUrl}>新規登録</a>へ
</p>
</form>
</Card>
</div>
Profile
ユーザー自身の情報を表示・編集するためのページです。
form/profile is coming soon.
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import ImageUploader from '$lib/components/ui/atoms/ImageUploader.svelte';
import Input from '$lib/components/ui/atoms/Input.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Select from '$lib/components/ui/atoms/Select.svelte';
import Textarea from '$lib/components/ui/atoms/Textarea.svelte';
import Calendar from '$lib/components/ui/modules/Calendar.svelte';
import { Calendar as CalendarIcon } from '@lucide/svelte';
let uid = $props.id();
let options = [
{ label: 'デザイナー', value: 'job1' },
{ label: 'エンジニア', value: 'job2' },
{ label: 'コーポレート', value: 'job3' },
];
const formValues = $state({
userImage: '',
userName: '',
job: '',
birthday: '',
bio: '',
});
// カレンダーの開閉状態
let isCalendarOpen = $state(false);
// カレンダー全体のルート要素(クリック判定用)
let calendarRoot = $state<HTMLDivElement>();
// InputDate に表示する値(yyyy/mm/dd形式)
let inputDateValue = $state('');
/**
* ユーザー情報を送信する
* @param e イベント
*/
function onSubmit(e: Event) {
e.preventDefault();
alert(JSON.stringify(formValues));
}
/** カレンダーを開く */
function openCalendar() {
isCalendarOpen = true;
}
/** カレンダーをトグルする(開く/閉じる) */
function toggleCalendar() {
isCalendarOpen = !isCalendarOpen;
}
/**
* カレンダーで日付を選択したときの処理
* @param val 日付
*/
function onChange(val: string) {
inputDateValue = val.replace(/-/g, '/');
if (inputDateValue) {
isCalendarOpen = false;
}
}
/**
* body クリック時の処理(カレンダーの外をクリックしたら閉じる)
* @param e クリックイベント
*/
function onClickBody(e: MouseEvent) {
if (!calendarRoot || !(e.target instanceof HTMLElement)) return;
if (!calendarRoot.contains(e.target)) {
isCalendarOpen = false;
}
}
/**
* フォーカスアウト時の処理(カレンダー外にフォーカスが移ったら閉じる)
* @param e フォーカスイベント
*/
function onFocusout(e: FocusEvent) {
if (!calendarRoot || !(e.relatedTarget instanceof HTMLElement)) return;
if (!calendarRoot.contains(e.relatedTarget)) {
isCalendarOpen = false;
}
}
/**
* Escapeキーでカレンダーを閉じる処理
* @param e キーボードイベント
*/
function onEscape(e: KeyboardEvent) {
if (isCalendarOpen && e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
isCalendarOpen = false;
}
}
</script>
<svelte:body onclickcapture={onClickBody} onkeydown={onEscape} />
<div class="flex flex-col justify-center w-full min-h-screen p-6">
<form class="flex flex-col max-w-144 w-full gap-4 mx-auto max-lg:max-w-none" onsubmit={onSubmit}>
<div>
<ImageUploader bind:src={formValues.userImage} />
</div>
<div>
<Label class="mb-2 cursor-pointer" for="user-name-{uid}" required>ユーザー名</Label>
<Input id="user-name-{uid}" placeholder="user123" required bind:value={formValues.userName} />
</div>
<div>
<Label class="mb-1.5 cursor-pointer" for="job-{uid}">職種</Label>
<Select id="job-{uid}" {options} placeholder="選択してください" bind:value={formValues.job} />
</div>
<div>
<Label class="mb-2 cursor-pointer" for="birthday-{uid}">誕生日</Label>
<div class="relative flex flex-col w-full" bind:this={calendarRoot} onfocusout={onFocusout}>
<Input
id="birthday-{uid}"
type="text"
placeholder="2000/04/01"
readonly
inputClass="cursor-pointer !opacity-100"
bind:value={inputDateValue}
onfocus={openCalendar}
>
{#snippet endContent()}
<button type="button" class="cursor-pointer" onclick={toggleCalendar}>
<CalendarIcon class="text-base-foreground-muted" size="1rem" />
</button>
{/snippet}
</Input>
{#if isCalendarOpen}
<Calendar class="absolute top-full mt-1" {onChange} bind:value={formValues.birthday} autoFocusDate />
{/if}
</div>
</div>
<div>
<Label class="mb-2 cursor-pointer" for="bio-{uid}">自己紹介</Label>
<Textarea id="bio-{uid}" placeholder="簡単な自己紹介を入力してください" bind:value={formValues.bio} />
</div>
<div class="flex items-center justify-end w-full gap-2 max-lg:flex-col-reverse">
<Button class="max-lg:w-full" type="button" variant="secondary" size="small">キャンセル</Button>
<Button class="max-lg:w-full" type="submit" size="small">保存</Button>
</div>
</form>
</div>
Contact
ユーザーが運営者へ質問や要望、問題報告を送るためのページです。
form/contact is coming soon.
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Checkbox from '$lib/components/ui/atoms/Checkbox.svelte';
import Input from '$lib/components/ui/atoms/Input.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Radio from '$lib/components/ui/atoms/Radio.svelte';
import Textarea from '$lib/components/ui/atoms/Textarea.svelte';
let uid = $props.id();
// お問い合わせ種別
let inquiryTypes = [
{ label: 'サービスについて', value: 'service' },
{ label: '不具合の報告', value: 'report' },
{ label: 'アカウントについて', value: 'account' },
];
// 回答方法
let responseMethods = [
{ label: 'メールでの返信を希望', value: 'mail' },
{ label: 'お電話での連絡を希望', value: 'tel' },
];
// お問い合わせ情報
const formValues = $state({
userName: '',
email: '',
tel: '',
selectedInquiryTypes: [],
responseMethod: 'mail',
contact: '',
});
/**
* お問い合わせ情報を送信する
* @param e イベント
*/
function onSubmit(e: Event) {
e.preventDefault();
alert(JSON.stringify(formValues));
}
</script>
<div class="flex flex-col justify-center w-full min-h-screen p-6">
<form class="flex flex-col max-w-144 w-full gap-6 mx-auto max-lg:max-w-none" onsubmit={onSubmit}>
<div>
<Label class="mb-2 cursor-pointer" for="name-{uid}" required>お名前</Label>
<Input id="name-{uid}" placeholder="山田 太郎" required bind:value={formValues.userName} />
</div>
<div>
<Label class="mb-2 cursor-pointer" for="email-{uid}" required>メールアドレス</Label>
<Input id="email-{uid}" type="email" placeholder="sample@example.jp" required bind:value={formValues.email} />
</div>
<div>
<Label class="mb-1.5 cursor-pointer" for="tel-{uid}" required>お電話番号</Label>
<Input
id="tel-{uid}"
type="tel"
pattern={'^(?:0[1-9]0-?[0-9]{4}-?[0-9]{4}|0[1-9]{3}-?[0-9]{2}-?[0-9]{4})$'}
placeholder="090-1234-5678"
inputmode="tel"
autocomplete="tel"
required
bind:value={formValues.tel}
/>
</div>
<div>
<Label class="mb-1.5 cursor-pointer" for="type-0-{uid}">お問い合わせ種別</Label>
<p class="text-base-foreground-muted text-sm mb-4">複数ご選択いただけます。</p>
<div class="grid gap-2">
{#each inquiryTypes as type, index}
<Label class="!flex items-center gap-2 group">
<Checkbox id="type-{index}-{uid}" value={type.value} bind:group={formValues.selectedInquiryTypes} /><span class="font-normal text-base-foreground-default text-sm/tight">{type.label}</span>
</Label>
{/each}
</div>
</div>
<div>
<Label class="mb-4 cursor-pointer" for="method-0-{uid}" required>ご希望の回答方法</Label>
<div class="grid gap-2">
{#each responseMethods as method, index}
<Label class="!flex items-center gap-2 group">
<Radio id="method-{index}-{uid}" name="response" value={method.value} required bind:group={formValues.responseMethod} /><span class="font-normal text-base-foreground-default text-sm/tight">{method.label}</span>
</Label>
{/each}
</div>
</div>
<div>
<Label class="mb-2 cursor-pointer" for="contact-{uid}" required>お問い合わせ内容</Label>
<Textarea id="contact-{uid}" placeholder="ログインできなくなった。" required bind:value={formValues.contact} />
</div>
<div class="flex items-center justify-end w-full gap-2 max-lg:flex-col-reverse">
<Button class="max-lg:w-full" type="button" variant="secondary" size="small">キャンセル</Button>
<Button class="max-lg:w-full" type="submit" size="small">送信</Button>
</div>
</form>
</div>