Skip to content

Instantly share code, notes, and snippets.

@wilsonowilson
Created February 6, 2023 08:38
Show Gist options
  • Save wilsonowilson/916b1bafad71827f0f217f2f6656f2db to your computer and use it in GitHub Desktop.
Save wilsonowilson/916b1bafad71827f0f217f2f6656f2db to your computer and use it in GitHub Desktop.
Svelte + Tailwind Image Picker
<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