Added some more dummy validating form inputs

This commit is contained in:
Samuel Andert 2023-07-31 14:53:50 +02:00
parent 120c6dd280
commit 3d2960390a
6 changed files with 292 additions and 38 deletions

View File

180
src/documentations/external/skeleton.md vendored Normal file
View File

@ -0,0 +1,180 @@
TYPESCRIPT
Copy
import { RangeSlider } from '@skeletonlabs/skeleton';
Source
Page Source
WAI-ARIA
Usage
Props
Slots
Events
Keyboard
Demo
Combines a native range input with datalist ticks to create a powerful range slider element.
Labeled
TYPESCRIPT
Copy
let value = 15;
let max = 25;
HTML
Copy
<RangeSlider name="range-slider" bind:value={value} max={25} step={1} ticked>
<div class="flex items-center justify-between">
<div class="font-bold">Label</div>
<div class="text-xs">{value} / {max}</div>
</div>
</RangeSlider>
This component implements restProps. This passes all extra attributes to the component's input elements.
Name Type Value Description
name string - Required. Set a unique name for the input.
id string - Provide a unique input id. Auto-generated by default
value number 0 Set the input value.
min number 0 Set the input minimum range.
max number 100 Set the input maximum range.
step number 1 Set the input step offset.
ticked boolean false Enables tick marks. See browser support below.
accent string 'accent-surface-900 dark:accent-surface-50' Provide classes to set the input accent color.
label string - A semantic ARIA label.
Usage
Props
Slots
Events
Keyboard
Name Default Fallback Description
default ✓ - -
trail - - A label slot directly below the range slider.
Source
Page Source
WAI-ARIA
Usage
Props
Slots
Events
Keyboard
Name Type Element Response Description
on:click forwarded <input> - -
on:change forwarded <input> - -
on:blur forwarded <input> - -
Source
Page Source
WAI-ARIA
Usage
Props
Slots
Events
Keyboard
Keys Description
Right Arrow or Up Arrow Increase the value of the slider by one step.
Left Arrow or Down Arrow Decrease the value of the slider by one step.
Home Set the slider to the first allowed value in its range.
End Set the slider to the last allowed value in its range.
Page Up Increase the slider value by a large amount.
Page Down Decrease the slider value by a large amount.
Slide Toggles
A sliding toggle element that can capture input from a user.
TYPESCRIPT
Copy
import { SlideToggle } from '@skeletonlabs/skeleton';
Source
Page Source
WAI-ARIA
Usage
Props
Slots
Events
Demo
TYPESCRIPT
Copy
let value: boolean = false;
HTML
Copy
<SlideToggle name="slide" bind:checked={value} />
This component provides an alternative UI for a checkbox input element.
Labeled
HTML
Copy
<SlideToggle name="slider-label" checked>(label)</SlideToggle>
Customized
Slide toggles styles and colors can be easily customized with the active and size properties.
HTML
Copy
<SlideToggle name="slider-large" checked active="bg-primary-500" size="lg" />
Checkbox Attributes
This component supports Svelte's $$restProps, which allows for required, disabled, and any other valid checkbox input attributes.
HTML
Copy
<SlideToggle ... required disabled />
Usage
Props
Slots
Events
This component implements restProps. This passes all extra attributes to the component's input elements.
Name Type Value Description
name string - Required. Set a unique name for the input.
checked boolean false The checked state of the input element.
size 'sm' | 'md' | 'lg' 'md' Sets the size of the component.
background string 'bg-surface-400 dark:bg-surface-700' Provide classes to set the inactive state background color.
active string 'bg-surface-900 dark:bg-surface-300' Provide classes to set the active state background color.
border string - Provide classes to set the border styles.
rounded string 'rounded-full' Provide classes to set border radius styles.
label string - Provide a semantic label.
TYPESCRIPT
Copy
import { SlideToggle } from '@skeletonlabs/skeleton';
Source
Page Source
WAI-ARIA
Usage
Props
Slots
Events
Name Default Fallback Description
default ✓ - -
Slide Toggles
A sliding toggle element that can capture input from a user.
TYPESCRIPT
Copy
import { SlideToggle } from '@skeletonlabs/skeleton';
Source
Page Source
WAI-ARIA
Usage
Props
Slots
Events
Name Type Element Response Description
on:keyup dispatched - { event } Fires when the component is focused and key is pressed.
on:click forwarded <input> - -
on:keydown forwarded <input> - -
on:keypress forwarded <input> - -
on:mouseover forwarded <input> - -
on:change forwarded <input> - -
on:focus forwarded <input> - -
on:blur forwarded <input> - -

View File

@ -3,8 +3,19 @@
import { UserSchema } from '$lib/types/UserSchema'; import { UserSchema } from '$lib/types/UserSchema';
import { afterUpdate } from 'svelte'; import { afterUpdate } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { RangeSlider, SlideToggle } from '@skeletonlabs/skeleton';
const initialFormData = { name: '', email: '' }; const initialFormData = {
name: '',
email: '',
about: '',
age: '',
favoriteFood: '',
slider: 0,
toggle: false
};
const fields = ['name', 'email', 'about', 'age', 'favoriteFood', 'slider', 'toggle'];
const { form, errors, validate, constraints, capture, restore } = superForm(initialFormData, { const { form, errors, validate, constraints, capture, restore } = superForm(initialFormData, {
validators: UserSchema, validators: UserSchema,
@ -50,7 +61,7 @@
}); });
</script> </script>
<div class="flex items-center justify-center min-h-screen"> <div class="flex items-center justify-center min-h-screen overflow-scroll">
{#if $successMessage} {#if $successMessage}
<!-- Display the success message instead of the form --> <!-- Display the success message instead of the form -->
<aside class="w-full max-w-md p-4 alert variant-ghost" id="message-container"> <aside class="w-full max-w-md p-4 alert variant-ghost" id="message-container">
@ -73,44 +84,77 @@
</aside> </aside>
{:else} {:else}
<form on:submit|preventDefault={handleSubmit} class="w-full max-w-md"> <form on:submit|preventDefault={handleSubmit} class="w-full max-w-md">
{#each fields as field}
<div class="mb-4"> <div class="mb-4">
{#if $errors.name} {#if $errors[field]}
<span class="block mb-2 font-semibold text-red-500">{$errors.name}</span> <span class="block mb-2 font-semibold text-red-500">{$errors[field]}</span>
{:else} {:else}
<label for="name" class="block mb-2 font-semibold text-white">Name</label> <label for={field} class="block mb-2 font-semibold text-white"
>{field.charAt(0).toUpperCase() + field.slice(1)}</label
>
{/if} {/if}
{#if field === 'about'}
<textarea
name={field}
class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={$form[field]}
aria-invalid={$errors[field] ? 'true' : undefined}
{...constraints[field]}
/>
{:else if field === 'favoriteFood'}
<select
name={field}
class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={$form[field]}
aria-invalid={$errors[field] ? 'true' : undefined}
{...constraints[field]}
>
<option value="">Select...</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="coconut">Coconut</option>
<option value="strawberry">Strawberry</option>
<option value="mango">Mango</option>
</select>
{:else if field === 'age'}
<input <input
name="name" name={field}
type="number"
class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={$form[field]}
aria-invalid={$errors[field] ? 'true' : undefined}
{...constraints[field]}
/>
{:else if field === 'slider'}
<RangeSlider name={field} bind:value={$form[field]} min={0} max={100} step={1} ticked>
<div class="flex items-center justify-between">
<div class="text-xs">{$form[field]} / 100</div>
</div>
</RangeSlider>
{:else if field === 'toggle'}
<SlideToggle name={field} bind:checked={$form[field]} />
{:else}
<input
name={field}
type="text" type="text"
class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={$form.name} bind:value={$form[field]}
aria-invalid={$errors.name ? 'true' : undefined} aria-invalid={$errors[field] ? 'true' : undefined}
{...constraints.name} {...constraints[field]}
/> />
</div>
<div class="mb-4">
{#if $errors.email}
<span class="block mb-2 font-semibold text-red-500">{$errors.email}</span>
{:else}
<label for="email" class="block mb-2 font-semibold text-white">Email</label>
{/if} {/if}
<input
name="email"
type="email"
class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={$form.email}
aria-invalid={$errors.email ? 'true' : undefined}
{...constraints.email}
/>
</div> </div>
{/each}
<button <button
type="submit" type="submit"
class="w-full px-4 py-2 mt-4 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 mt-4 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={$errors.name || $errors.email} disabled={$errors.name ||
$errors.email ||
$errors.about ||
$errors.age ||
$errors.favoriteFood}
> >
Submit Submit
</button> </button>

View File

@ -5,10 +5,10 @@
let isStoreLoaded = false; let isStoreLoaded = false;
$: if (services && services.helloEarthAlert) { $: if (services && services.helloEarthAlert) {
services.helloEarthAlert.alertMe(); // services.helloEarthAlert.alertMe();
} }
$: if (services.core) { $: if (services.core) {
services.core.testAlert(); // services.core.testAlert();
} }
$: if ($store) isStoreLoaded = true; $: if ($store) isStoreLoaded = true;

View File

@ -0,0 +1,8 @@
/src/lib/components/examples/Form.svelte:98:7 'type' attribute cannot be dynamic if input uses two-way binding
/src/lib/components/examples/Form.svelte:98:7
96 | <input
97 | name={field}
98 | type={field === 'age' ? 'number' : 'text'}
^
99 | class="w-full px-3 py-2 bg-transparent border-gray-100 rounded-md border-1 ring-0 ring-white focus:outline-none focus:ring-2 focus:ring-blue-500"
100 | bind:value={$form[field]}

View File

@ -9,9 +9,31 @@ const validationMessages = {
email: { email: {
isEmail: 'Invalid email address.', isEmail: 'Invalid email address.',
}, },
about: {
maxLength: 'About me section must contain at most 500 characters.',
},
age: {
min: 'Age must be at least 18.',
max: 'Age must be at most 120.',
},
favoriteFood: {
invalid: 'Invalid food selection.',
},
slider: {
min: 'Slider value must be at least 0.',
max: 'Slider value must be at most 100.',
},
toggle: {
isBoolean: 'Toggle value must be a boolean.',
},
}; };
export const UserSchema = z.object({ export const UserSchema = z.object({
name: z.string().min(3, validationMessages.name.minLength).max(10, validationMessages.name.maxLength), name: z.string().min(3, validationMessages.name.minLength).max(10, validationMessages.name.maxLength),
email: z.string().email(validationMessages.email.isEmail), email: z.string().email(validationMessages.email.isEmail),
about: z.string().max(500, validationMessages.about.maxLength),
age: z.number().min(18, validationMessages.age.min).max(120, validationMessages.age.max),
favoriteFood: z.enum(['apple', 'banana', 'coconut', 'strawberry', 'mango']).refine(value => value !== '', validationMessages.favoriteFood.invalid),
slider: z.number().min(0, validationMessages.slider.min).max(100, validationMessages.slider.max),
toggle: z.boolean().refine(value => typeof value === 'boolean', validationMessages.toggle.isBoolean),
}); });