Created
February 11, 2021 05:29
-
-
Save RedHatter/19e8fb4b5c7606258d938353542adf18 to your computer and use it in GitHub Desktop.
Svelte form and input components with buildt-in validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
export let checked | |
export let name | |
let className | |
export { className as class } | |
</script> | |
<style> | |
input + div { | |
border: 1px solid rgb(206, 212, 218); | |
transition: all ease-out 0.2s; | |
&:after { | |
position: absolute 48% 38%; | |
width: 0; | |
height: 7px; | |
border-bottom: 2px solid var(--dusty-orange); | |
border-left: 2px solid var(--dusty-orange); | |
content: ''; | |
opacity: 0; | |
transition: all ease-out 0.2s; | |
transform: rotate(-45deg); | |
transform-origin: bottom left; | |
} | |
} | |
input:checked + div:after { | |
width: 0.8rem; | |
opacity: 1; | |
} | |
</style> | |
<label class="{className} flex cursor-pointer"> | |
<input class="hidden" type="checkbox" {name} on:change bind:checked /> | |
<div | |
class="relative flex-grow-0 flex-shrink-0 mr-2 w-4 h-4 rounded bg-white" /> | |
<div> | |
<slot /> | |
</div> | |
</label> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script context="module"> | |
import { getContext as _getContext, setContext as _setContext } from 'svelte' | |
// Use object literal to avoid conflicts. A Symbol would be better but is | |
// unsupported by IE | |
const key = {} | |
export const getContext = _getContext.bind(undefined, key) | |
export const setContext = _setContext.bind(undefined, key) | |
</script> | |
<script> | |
import { createEventDispatcher } from 'svelte' | |
export let action | |
export let valid = true | |
let className | |
export { className as class } | |
const dispatch = createEventDispatcher() | |
const validatorList = [] | |
const context = { | |
register: fn => validatorList.push(fn), | |
unregister: fn => { | |
let i = validatorList.indexOf(fn) | |
if (i != -1) validatorList.splice(i, 1) | |
}, | |
validate: () => { | |
valid = validatorList.filter(fn => !fn()).length == 0 | |
dispatch(valid ? 'valid' : 'invalid') | |
return valid | |
} | |
} | |
setContext(context) | |
function onSubmit(e) { | |
if (context.validate()) { | |
if (!action) e.preventDefault() | |
dispatch('submit') | |
} else { | |
e.preventDefault() | |
} | |
} | |
</script> | |
<form on:submit={onSubmit} class={className} {action}> | |
<slot /> | |
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { onMount } from 'svelte' | |
import { getContext } from './Form.svelte' | |
export let disabled | |
export let id | |
let className | |
export { className as class } | |
export let value = '' | |
export let required = false | |
let valid = true | |
function validate() { | |
return (valid = !(required && (value === '' || value === null))) | |
} | |
const context = getContext() | |
onMount(() => { | |
if (!context) return | |
context.register(validate) | |
return () => context.unregister(validate) | |
}) | |
$: value, !valid && validate() && context.validate() | |
</script> | |
<style> | |
select { | |
background: #ffffff | |
url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") | |
no-repeat right 0.75rem center/8px 10px; | |
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; | |
&:focus { | |
border-color: #80bdff; | |
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); | |
} | |
} | |
</style> | |
<div class={className}> | |
<select | |
class="py-2 pr-6 pl-4 w-full border rounded cursor-pointer appearance-none" | |
{disabled} | |
{id} | |
bind:value | |
on:change> | |
<option value="">Select an option</option> | |
<slot /> | |
</select> | |
{#if !valid} | |
<span class="text-red-600 text-sm">This field is required</span> | |
{/if} | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { createEventDispatcher } from 'svelte' | |
const dispatch = createEventDispatcher() | |
export let value = false | |
export let onLabel = 'On' | |
export let offLabel = 'Off' | |
let className | |
export { className as class } | |
$: dispatch('change', value) | |
</script> | |
<style> | |
label { | |
cursor: pointer; | |
} | |
input { | |
display: none; | |
} | |
.switch { | |
position: relative; | |
display: inline-block; | |
width: 40px; | |
height: 20px; | |
border: 1px solid var(--cool-gray); | |
border-radius: 20px; | |
background-color: white; | |
vertical-align: text-bottom; | |
marign: 0 5px; | |
} | |
.switch::after { | |
display: block; | |
margin: 1px; | |
width: 16px; | |
height: 16px; | |
border-radius: 50%; | |
background-color: var(--cool-gray); | |
content: ''; | |
} | |
input:checked ~ .switch { | |
border-color: var(--dodger-blue); | |
background-color: var(--dodger-blue); | |
} | |
input:checked ~ .switch::after { | |
background-color: #ffffff; | |
transform: translateX(20px); | |
} | |
.on-label, | |
input:checked ~ .off-label { | |
opacity: 0.3; | |
} | |
input:checked ~ .on-label, | |
.off-label { | |
opacity: 1; | |
} | |
.on-label, | |
.off-label, | |
.switch, | |
.switch::after { | |
transition: all 0.15s ease-in-out; | |
} | |
</style> | |
<label class={className}> | |
<input type="checkbox" bind:checked={value} /> | |
<span class="off-label">{offLabel}</span> | |
<span class="switch" /> | |
<span class="on-label">{onLabel}</span> | |
</label> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { onMount } from 'svelte' | |
import { getContext } from './Form.svelte' | |
import { focusWithin } from '../actions.js' | |
export let value = '' | |
export let disabled | |
export let readonly | |
export let id | |
export let name | |
export let size | |
export let placeholder | |
let className | |
export { className as class } | |
export let unit | |
export let type = 'text' | |
export let required = false | |
export let errorMessage | |
export let min | |
export let max | |
export let pattern | |
export let oneOf | |
let _errorMessage = '' | |
function validate() { | |
_errorMessage = | |
required && (value === '' || value === null) | |
? 'This field is required' | |
: value === '' || value === null | |
? '' | |
: oneOf != undefined && | |
((Array.isArray(oneOf) && !oneOf.includes(value)) || value != oneOf) | |
? 'Not a valid value' | |
: pattern instanceof RegExp && !pattern.test(value) | |
? 'Does not match the required format' | |
: min != undefined && value < min | |
? 'Must be larger than ' + min | |
: max != undefined && value > max | |
? 'Must be smaller than ' + max | |
: '' | |
return !_errorMessage | |
} | |
const context = getContext() | |
onMount(() => { | |
if (!context) return | |
context.register(validate) | |
return () => context.unregister(validate) | |
}) | |
let attrs | |
$: { | |
// github:sveltejs/svelte#1434 | |
attrs = { id, name, disabled, readonly } | |
if (size) attrs.size = size | |
if (placeholder) attrs.placeholder = placeholder | |
} | |
$: value, _errorMessage && validate() && context.validate() | |
</script> | |
<style> | |
input { | |
flex-grow: 1; | |
min-width: 0; | |
outline: none; | |
border: none; | |
background-color: transparent; | |
background-image: none; | |
} | |
input[type='number'] { | |
-moz-appearance: textfield; | |
&::-webkit-outer-spin-button, | |
&::-webkit-inner-spin-button { | |
margin: 0; | |
-webkit-appearance: none; | |
} | |
} | |
.plain { | |
border-bottom: 2px dashed var(--border); | |
text-align: inherit; | |
font-family: monospace; | |
} | |
</style> | |
<div class="{className} relative"> | |
{#if type == 'plain'} | |
<input class="plain pb-2" {...attrs} bind:value on:change /> | |
{:else} | |
<div class="text-field" use:focusWithin> | |
{#if type == 'number'} | |
<input | |
class="p-0" | |
{...attrs} | |
{value} | |
type="number" | |
pattern="\d*" | |
on:change | |
on:input={e => { | |
value = e.target.value.replace(/\D/g, '') | |
e.target.value = value | |
}} /> | |
{:else if type == 'password'} | |
<input class="p-0" type="password" {...attrs} bind:value on:change /> | |
{:else} | |
<input class="p-0" {...attrs} bind:value on:change /> | |
{/if} | |
{#if unit} | |
<span class="text-muted">{unit}</span> | |
{/if} | |
<slot /> | |
</div> | |
{/if} | |
{#if _errorMessage} | |
<span class="text-red-600 text-sm">{errorMessage || _errorMessage}</span> | |
{/if} | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment