Created
February 6, 2023 08:38
-
-
Save wilsonowilson/916b1bafad71827f0f217f2f6656f2db to your computer and use it in GitHub Desktop.
Svelte + Tailwind Image Picker
This file contains hidden or 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 lang="ts"> | |
import { toast } from "../utils/toast"; | |
import Icon from "../components/Icon.svelte"; | |
import { ImagePlus, X } from "../components/icons"; | |
import Label from "../components/Label.svelte"; | |
import FileDrop from "filedrop-svelte"; | |
import { scale } from "svelte/transition"; | |
export let files: File[] = []; | |
export let label: string = ""; | |
export let accept = "image/png,image/jpg,image/gif,image/jpeg,image/webp"; | |
export let maxItems = 4; | |
let previewItems: { url: string; file: File }[] = []; | |
function handleFilesSelect(e: { detail: { files: any } }) { | |
const accepted = e.detail.files?.accepted as any; | |
if (!accepted || !accepted.length) return; | |
if (accepted.length + files.length > maxItems) { | |
toast.error(`You can only attach up to ${maxItems} images`); | |
return; | |
} | |
if (accepted) { | |
files = accepted; | |
accepted.forEach((file: File) => { | |
const url = URL.createObjectURL(file); | |
previewItems.push({ url, file }); | |
}); | |
previewItems = [...previewItems]; | |
} | |
} | |
function removeFile(file: File) { | |
files = files.filter((f) => f !== file); | |
previewItems = previewItems.filter((f) => f.file !== file); | |
} | |
</script> | |
<div> | |
{#if label} | |
<div class="mb-2"> | |
<Label {label} /> | |
</div> | |
{/if} | |
{#if !files.length} | |
<div | |
class="h-32 w-full cursor-pointer rounded-md border border-dashed border-gray-300 p-4 duration-200 " | |
> | |
<FileDrop | |
fileLimit={4} | |
maxSize={2 * 1024 * 1024} | |
multiple | |
{accept} | |
on:filedrop={handleFilesSelect} | |
> | |
<div | |
class="flex h-full cursor-pointer flex-col items-center justify-center gap-2" | |
> | |
<Icon data={ImagePlus} class="text-gray-500" size={20} /> | |
<p class="text-sm text-gray-400"> | |
Max file size: 2MB, accepted: jpeg, jpg, png, gif | |
</p> | |
</div> | |
</FileDrop> | |
</div> | |
{:else} | |
<div | |
class="h-32 w-full rounded-md border border-dashed border-gray-300 p-4 duration-200 " | |
> | |
<div class="flex w-full gap-2 overflow-x-auto"> | |
<FileDrop | |
maxSize={2 * 1024 * 1024} | |
multiple={false} | |
{accept} | |
on:filedrop={handleFilesSelect} | |
> | |
<div | |
class="flex h-24 w-24 flex-none flex-col items-center justify-center rounded-md border border-dashed border-gray-300 p-4 duration-200 hover:scale-[1.01] hover:bg-gray-50" | |
> | |
<Icon data={ImagePlus} class="text-gray-500" size={20} /> | |
</div> | |
</FileDrop> | |
{#each previewItems as item, i (item.file)} | |
<div | |
in:scale={{ duration: 200, delay: i * 50, start: 0.8 }} | |
style="isolation: isolate;" | |
class="relative flex h-24 w-24 flex-none gap-2 overflow-hidden rounded-md" | |
> | |
<img | |
style="isolation: isolate;" | |
src={item.url} | |
class="h-full w-full object-cover" | |
alt="" | |
/> | |
<div | |
class="absolute inset-0 flex h-full w-full items-start justify-end p-2" | |
> | |
<button | |
on:click={() => { | |
removeFile(item.file); | |
}} | |
type="button" | |
class="rounded-full bg-white p-1.5 shadow-md duration-200" | |
> | |
<Icon data={X} class="text-gray-500" size={12} /> | |
</button> | |
</div> | |
</div> | |
{/each} | |
</div> | |
</div> | |
{/if} | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment