You've already forked AstralRinth
forked from didirus/AstralRinth
Add FileUpload component
This commit is contained in:
@@ -3,14 +3,14 @@
|
|||||||
import IconMoon from 'virtual:icons/heroicons-outline/moon'
|
import IconMoon from 'virtual:icons/heroicons-outline/moon'
|
||||||
import IconSun from 'virtual:icons/heroicons-outline/sun'
|
import IconSun from 'virtual:icons/heroicons-outline/sun'
|
||||||
|
|
||||||
export let meta: { raised: boolean }
|
export let meta: { raised: boolean; column: boolean }
|
||||||
|
|
||||||
let theme = 'light'
|
let theme = 'light'
|
||||||
let background = meta.raised ? 'var(--color-raised-bg)' : 'var(--color-bg)'
|
let background = meta.raised ? 'var(--color-raised-bg)' : 'var(--color-bg)'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="example">
|
<div class="example">
|
||||||
<div class="example__preview theme-{theme} base" style:background>
|
<div class="example__preview theme-{theme} base" class:column={meta.column} style:background>
|
||||||
<slot name="example" />
|
<slot name="example" />
|
||||||
</div>
|
</div>
|
||||||
<div class="example__source">
|
<div class="example__source">
|
||||||
@@ -44,6 +44,10 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
||||||
|
&.column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__source {
|
&__source {
|
||||||
|
|||||||
31
docs/routes/components/FileUpload.md
Normal file
31
docs/routes/components/FileUpload.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
### Single constrained example
|
||||||
|
|
||||||
|
```svelte example raised column
|
||||||
|
<script lang="ts">
|
||||||
|
import { Field, FileUpload } from 'omorphia'
|
||||||
|
|
||||||
|
let file: File
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field label="Upload image">
|
||||||
|
<FileUpload accept="image/*" constrained bind:file />
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
File name: {file?.name}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple example
|
||||||
|
|
||||||
|
```svelte example raised column
|
||||||
|
<script lang="ts">
|
||||||
|
import { Field, FileUpload } from 'omorphia'
|
||||||
|
|
||||||
|
let files: File[] = []
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field label="Upload file">
|
||||||
|
<FileUpload accept="*" multiple bind:files />
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
Count: {files.length}
|
||||||
|
```
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
min-width: 2rem;
|
min-width: min-content;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
118
src/components/FileUpload.svelte
Normal file
118
src/components/FileUpload.svelte
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import IconTrash from 'virtual:icons/heroicons-outline/trash'
|
||||||
|
import IconUpload from 'virtual:icons/heroicons-outline/upload'
|
||||||
|
import IconFile from 'virtual:icons/lucide/file'
|
||||||
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
import Button from 'omorphia/components/Button.svelte'
|
||||||
|
import { classCombine } from 'omorphia/utils/classCombine'
|
||||||
|
|
||||||
|
export let multiple = false
|
||||||
|
export let accept: string
|
||||||
|
/** Prevents width from expanding due to large file names or images */
|
||||||
|
export let constrained = false
|
||||||
|
|
||||||
|
export let files: File[] = []
|
||||||
|
export let file: File | undefined
|
||||||
|
$: if (files) file = files[0] || undefined
|
||||||
|
|
||||||
|
let inputElement: HTMLInputElement
|
||||||
|
|
||||||
|
function addFiles(fileList: FileList) {
|
||||||
|
for (const file of Array.from(fileList)) {
|
||||||
|
// Check for duplicate files
|
||||||
|
if (!files.map((file) => file.name).includes(file.name)) {
|
||||||
|
files = [...files, file]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={classCombine(['file-dropzone', constrained && 'file-dropzone--constrained'])}>
|
||||||
|
{#if !file || multiple}
|
||||||
|
<div
|
||||||
|
class="file-dropzone__input"
|
||||||
|
on:drop|preventDefault={(event) => addFiles(event.dataTransfer.files)}
|
||||||
|
on:dragover|preventDefault
|
||||||
|
on:click={() => {
|
||||||
|
if (inputElement) inputElement.click()
|
||||||
|
}}>
|
||||||
|
<IconUpload />
|
||||||
|
{$t('images.how_to')}
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
{multiple}
|
||||||
|
{accept}
|
||||||
|
style:display="none"
|
||||||
|
bind:this={inputElement}
|
||||||
|
on:change={() => addFiles(inputElement.files)} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each files as file (file.name)}
|
||||||
|
<div class="file">
|
||||||
|
<div class="file__tab">
|
||||||
|
<IconFile />
|
||||||
|
<div class="file__tab__name"><b>{file.name}</b></div>
|
||||||
|
<Button
|
||||||
|
color="tertiary"
|
||||||
|
on:click={() => {
|
||||||
|
files = files.filter((it) => it.name !== file.name)
|
||||||
|
}}><IconTrash /> Remove</Button>
|
||||||
|
</div>
|
||||||
|
{#if file.type.startsWith('image/')}
|
||||||
|
<img class="file__preview" src={URL.createObjectURL(file)} alt="Uploaded file preview" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.file-dropzone {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
&--constrained {
|
||||||
|
width: 27rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
display: flex;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background-color: var(--color-button-bg);
|
||||||
|
border-radius: var(--rounded-sm);
|
||||||
|
border: dashed 0.3rem var(--color-text-lightest);
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
box-shadow: var(--shadow-inset);
|
||||||
|
border-radius: var(--rounded);
|
||||||
|
background-color: var(--color-button-bg);
|
||||||
|
|
||||||
|
&__tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__preview {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: var(--rounded-bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,8 @@ export { default as Chips } from './components/Chips.svelte'
|
|||||||
|
|
||||||
export { default as Field } from './components/Field.svelte'
|
export { default as Field } from './components/Field.svelte'
|
||||||
|
|
||||||
|
export { default as FileUpload } from './components/FileUpload.svelte'
|
||||||
|
|
||||||
export { default as Modal } from './components/Modal.svelte'
|
export { default as Modal } from './components/Modal.svelte'
|
||||||
|
|
||||||
export { default as ModalDeletion } from './components/ModalDeletion.svelte'
|
export { default as ModalDeletion } from './components/ModalDeletion.svelte'
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
|
|||||||
Reference in New Issue
Block a user