mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
feat(frontend): Add image app component (#1213)
* fix(frontend): hard type app component config * feat(frontend): Add image app component * feat(frontend): Add upload app input type
This commit is contained in:
@@ -27,6 +27,13 @@ export type Schema = {
|
||||
|
||||
export type Meta = { ownerKind: OwnerKind; owner: string; name: string }
|
||||
|
||||
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
|
||||
? Acc[number]
|
||||
: Enumerate<N, [...Acc, Acc['length']]>
|
||||
|
||||
/** An inclusive range of integer numbers */
|
||||
export type IntRange<F extends number, T extends number> = F | Exclude<Enumerate<T>, Enumerate<F>> | T
|
||||
|
||||
export function pathToMeta(path: string): Meta {
|
||||
const splitted = path.split('/')
|
||||
let ownerKind: OwnerKind
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
type FitOption = typeof staticValues['objectFitOptions'][number]
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['loading']
|
||||
|
||||
const fit: Record<FitOption, string> = {
|
||||
cover: 'object-cover',
|
||||
contain: 'object-contain',
|
||||
fill: 'object-fill',
|
||||
}
|
||||
|
||||
let source: string | undefined = undefined
|
||||
let imageFit: FitOption | undefined = undefined
|
||||
let altText: string | undefined = undefined
|
||||
let customStyles: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.source} bind:value={source} />
|
||||
<InputValue {id} input={configuration.imageFit} bind:value={imageFit} />
|
||||
<InputValue {id} input={configuration.altText} bind:value={altText} />
|
||||
<InputValue {id} input={configuration.customStyles} bind:value={customStyles} />
|
||||
|
||||
<img
|
||||
src={source}
|
||||
alt={altText}
|
||||
style={customStyles}
|
||||
class="w-full h-full {fit[imageFit || 'cover']}"
|
||||
>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput, EvalAppInput } from '../../inputType'
|
||||
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
setTimeout(() => (value = getValue(input)), 0)
|
||||
} else if (input.type == 'eval') {
|
||||
setTimeout(() => ((value = evalExpr(input as EvalAppInput)), 0))
|
||||
} else if (input.type == 'upload') {
|
||||
setTimeout(() => ((value = (input as UploadAppInput).value), 0))
|
||||
} else {
|
||||
setTimeout(() => (value = undefined), 0)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
DollarSign,
|
||||
SeparatorHorizontal,
|
||||
SeparatorVertical,
|
||||
Paperclip
|
||||
Paperclip,
|
||||
Image
|
||||
} from 'lucide-svelte'
|
||||
import type { Size } from 'svelte-grid'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -107,19 +108,25 @@
|
||||
| HorizontalDividerComponent
|
||||
| VerticalDividerComponent
|
||||
| FileInputComponent
|
||||
| ImageComponent
|
||||
)
|
||||
|
||||
// Dimensions key formula: <mobile width>:<mobile height>-<desktop width>:<desktop height>
|
||||
export const components: Record<
|
||||
AppComponent['type'],
|
||||
{
|
||||
export type AppComponentDimensions =
|
||||
`${IntRange<1, typeof NARROW_GRID_COLUMNS>}:${number}-${IntRange<1, typeof WIDE_GRID_COLUMNS>}:${number}`
|
||||
|
||||
export type AppComponentConfig = {
|
||||
name: string
|
||||
icon: any
|
||||
dims: `${number}:${number}-${number}:${number}`
|
||||
/**
|
||||
* Dimensions key formula:
|
||||
* [**mobile width**]:[**mobile height**]-[**desktop width**]:[**desktop height**]
|
||||
*/
|
||||
dims: AppComponentDimensions
|
||||
data: AppComponent
|
||||
cssIds?: string[]
|
||||
}
|
||||
> = {
|
||||
|
||||
export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
displaycomponent: {
|
||||
name: 'Rich Result',
|
||||
icon: Monitor,
|
||||
@@ -834,7 +841,7 @@
|
||||
slidercomponent: {
|
||||
name: 'Slider',
|
||||
icon: SlidersHorizontal,
|
||||
dims: '4:1-4:1',
|
||||
dims: '3:1-4:1',
|
||||
data: {
|
||||
softWrap: true,
|
||||
verticalAlignment: 'center',
|
||||
@@ -874,7 +881,7 @@
|
||||
rangecomponent: {
|
||||
name: 'Range',
|
||||
icon: SlidersHorizontal,
|
||||
dims: '4:2-4:2',
|
||||
dims: '3:2-4:2',
|
||||
data: {
|
||||
softWrap: true,
|
||||
verticalAlignment: 'center',
|
||||
@@ -1099,6 +1106,49 @@
|
||||
card: false
|
||||
}
|
||||
},
|
||||
imagecomponent: {
|
||||
name: 'Image',
|
||||
icon: Image,
|
||||
dims: '3:4-5:4',
|
||||
data: {
|
||||
id: '',
|
||||
type: 'imagecomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
source: {
|
||||
type: 'static',
|
||||
value: '/logo.svg',
|
||||
fieldType: 'text',
|
||||
fileUpload: {
|
||||
accept: 'image/*',
|
||||
base64: true
|
||||
}
|
||||
},
|
||||
imageFit: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'objectFitOptions',
|
||||
value: 'contain'
|
||||
},
|
||||
altText: {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'text',
|
||||
onlyStatic: true,
|
||||
tooltip: 'This text will appear if the image can\'t be loaded for any reason'
|
||||
},
|
||||
customStyles: {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'textarea',
|
||||
onlyStatic: true,
|
||||
},
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const inputs: ComponentSet = {
|
||||
@@ -1127,6 +1177,7 @@
|
||||
components: [
|
||||
'textcomponent',
|
||||
'iconcomponent',
|
||||
'imagecomponent',
|
||||
'htmlcomponent',
|
||||
'tablecomponent',
|
||||
'barchartcomponent',
|
||||
@@ -1509,6 +1560,9 @@
|
||||
import AppCurrencyInput from '../components/numberInputs/AppCurrencyInput.svelte'
|
||||
import AppDivider from '../components/AppDivider.svelte'
|
||||
import AppFileInput from '../components/otherInputs/AppFileInput.svelte'
|
||||
import type { IntRange } from '../../../common'
|
||||
import type { NARROW_GRID_COLUMNS, WIDE_GRID_COLUMNS } from '../gridUtils'
|
||||
import AppImage from '../components/dataDisplay/AppImage.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -1674,6 +1728,8 @@
|
||||
<AppIcon {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'fileinputcomponent'}
|
||||
<AppFileInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'imagecomponent'}
|
||||
<AppImage {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,5 +9,6 @@ export const staticValues = {
|
||||
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
|
||||
textStyleOptions: ['Title', 'Subtitle', 'Body', 'Label', 'Caption'],
|
||||
currencyOptions: ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY', 'CNY', 'INR', 'BRL'],
|
||||
localeOptions: ['en-US', 'en-GB', 'en-IE', 'de-DE', 'fr-FR', 'br-FR', 'ja-JP', 'pt-TL', 'fr-CA', 'en-CA']
|
||||
localeOptions: ['en-US', 'en-GB', 'en-IE', 'de-DE', 'fr-FR', 'br-FR', 'ja-JP', 'pt-TL', 'fr-CA', 'en-CA'],
|
||||
objectFitOptions: ['contain', 'cover', 'fill']
|
||||
} as const
|
||||
|
||||
@@ -14,43 +14,45 @@
|
||||
export let component: AppComponent
|
||||
</script>
|
||||
|
||||
<PanelSection title="Alignment">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#if component.horizontalAlignment}
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="text-xs font-semibold">Horizontal</div>
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={component.horizontalAlignment}>
|
||||
<ToggleButton position="left" value="left" size="xs">
|
||||
<AlignStartVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="right" size="xs">
|
||||
<AlignEndVertical size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
{#if component.horizontalAlignment || component.verticalAlignment}
|
||||
<PanelSection title="Alignment">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#if component.horizontalAlignment}
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="text-xs font-semibold">Horizontal</div>
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={component.horizontalAlignment}>
|
||||
<ToggleButton position="left" value="left" size="xs">
|
||||
<AlignStartVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="right" size="xs">
|
||||
<AlignEndVertical size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if component.type !== 'formcomponent' && component.verticalAlignment}
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="text-xs font-semibold">Vertical</div>
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={component.verticalAlignment}>
|
||||
<ToggleButton position="left" value="top" size="xs">
|
||||
<AlignStartHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="bottom" size="xs">
|
||||
<AlignEndHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
{#if component.type !== 'formcomponent' && component.verticalAlignment}
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="text-xs font-semibold">Vertical</div>
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={component.verticalAlignment}>
|
||||
<ToggleButton position="left" value="top" size="xs">
|
||||
<AlignStartHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="bottom" size="xs">
|
||||
<AlignEndHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
EvalAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UploadAppInput,
|
||||
UserAppInput
|
||||
} from '../../inputType'
|
||||
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
|
||||
import EvalInputEditor from './inputEditor/EvalInputEditor.svelte'
|
||||
import RowInputEditor from './inputEditor/RowInputEditor.svelte'
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
import UploadInputEditor from './inputEditor/UploadInputEditor.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput:
|
||||
@@ -18,6 +20,7 @@
|
||||
| UserAppInput
|
||||
| RowAppInput
|
||||
| EvalAppInput
|
||||
| UploadAppInput
|
||||
export let hasRows: boolean = false
|
||||
</script>
|
||||
|
||||
@@ -29,6 +32,8 @@
|
||||
<StaticInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'eval'}
|
||||
<EvalInputEditor {hasRows} {id} bind:componentInput />
|
||||
{:else if componentInput.type === 'upload'}
|
||||
<UploadInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'user'}
|
||||
<span class="text-2xs italic text-gray-6f00">Field's value is set by the user</span>
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Badge, ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import { addWhitespaceBeforeCapitals, capitalize } from '$lib/utils'
|
||||
import { faArrowRight, faPen, faTableCells, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faArrowRight, faPen, faUpload, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import { fieldTypeToTsType } from '../../utils'
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
@@ -94,6 +94,18 @@
|
||||
<svelte:fragment slot="text">User Input</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if 'fileUpload' in inputSpecs[inputSpecKey]}
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="upload"
|
||||
startIcon={{ icon: faUpload }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">Upload</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { FileInput } from "../../../../common"
|
||||
import type { UploadAppInput } from "../../../inputType"
|
||||
|
||||
export let componentInput: UploadAppInput | undefined
|
||||
</script>
|
||||
|
||||
<FileInput
|
||||
accept={componentInput?.fileUpload?.accept}
|
||||
multiple={componentInput?.fileUpload?.multiple}
|
||||
convertToBase64={componentInput?.fileUpload?.base64}
|
||||
iconSize={24}
|
||||
class="text-sm py-4"
|
||||
on:change={({detail}) => {
|
||||
if(componentInput) {
|
||||
componentInput.value = componentInput?.fileUpload?.multiple
|
||||
? detail
|
||||
: detail?.[0]
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="selected-title">
|
||||
<!-- Removing the title when there is a selected file -->
|
||||
<span></span>
|
||||
</svelte:fragment>
|
||||
</FileInput>
|
||||
@@ -7,9 +7,12 @@ const Breakpoints = {
|
||||
lg: 1024
|
||||
}
|
||||
|
||||
const WIDE_GRID_COLUMNS = 12 as const;
|
||||
const NARROW_GRID_COLUMNS = 3 as const;
|
||||
|
||||
const columnConfiguration: ColumnConfiguration = [
|
||||
[Breakpoints.lg, 12],
|
||||
[Breakpoints.sm, 3]
|
||||
[Breakpoints.lg, WIDE_GRID_COLUMNS],
|
||||
[Breakpoints.sm, NARROW_GRID_COLUMNS]
|
||||
]
|
||||
|
||||
const gridColumns = columnConfiguration.map((value) => value[1])
|
||||
@@ -51,6 +54,8 @@ function enableDrag(component: GridItem): GridItem {
|
||||
|
||||
export {
|
||||
gridColumns,
|
||||
WIDE_GRID_COLUMNS,
|
||||
NARROW_GRID_COLUMNS,
|
||||
columnConfiguration,
|
||||
disableDrag,
|
||||
enableDrag,
|
||||
|
||||
@@ -35,7 +35,11 @@ export type UserInput<U> = {
|
||||
value: U | undefined
|
||||
}
|
||||
|
||||
|
||||
// Input can be uploaded with a file selector
|
||||
export type UploadInput = {
|
||||
type: 'upload'
|
||||
value: string
|
||||
}
|
||||
|
||||
export type EvalInput = {
|
||||
type: 'eval'
|
||||
@@ -88,6 +92,7 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
| UserInput<U>
|
||||
| RowInput
|
||||
| EvalInput
|
||||
| UploadInput
|
||||
| ResultInput
|
||||
| TemplateInput
|
||||
) &
|
||||
@@ -96,7 +101,28 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
type InputConfiguration<T extends InputType, U, V extends InputType> = {
|
||||
fieldType: T
|
||||
subFieldType?: V
|
||||
format?: string | undefined
|
||||
format?: string | undefined,
|
||||
fileUpload?: {
|
||||
/** Use `*` to accept anything. */
|
||||
accept: string
|
||||
/**
|
||||
* Controls if user is allowed to select multiple files.
|
||||
* @default false
|
||||
*/
|
||||
multiple?: boolean
|
||||
/**
|
||||
* Controls if the uploaded file(s) will be returned as `Base64` strings.
|
||||
* @default false
|
||||
*/
|
||||
base64?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
type StaticOptions = {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
}
|
||||
|
||||
export type AppInput =
|
||||
@@ -111,12 +137,7 @@ export type AppInput =
|
||||
| AppInputSpec<'any', any>
|
||||
| AppInputSpec<'object', Record<string | number, any>>
|
||||
| AppInputSpec<'object', string>
|
||||
| (AppInputSpec<'select', string> & {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| (AppInputSpec<'select', string> & StaticOptions)
|
||||
| AppInputSpec<'icon-select', string>
|
||||
| AppInputSpec<'array', string[], 'text'>
|
||||
| AppInputSpec<'array', string[], 'textarea'>
|
||||
@@ -126,9 +147,7 @@ export type AppInput =
|
||||
| AppInputSpec<'array', string[], 'time'>
|
||||
| AppInputSpec<'array', string[], 'datetime'>
|
||||
| AppInputSpec<'array', object[], 'object'>
|
||||
| (AppInputSpec<'array', string[], 'select'> & {
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
|
||||
|
||||
export type RowAppInput = Extract<AppInput, { type: 'row' }>
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
@@ -136,5 +155,6 @@ export type ConnectedAppInput = Extract<AppInput, { type: 'connected' }>
|
||||
export type UserAppInput = Extract<AppInput, { type: 'user' }>
|
||||
export type ResultAppInput = Extract<AppInput, { type: 'runnable' }>
|
||||
export type EvalAppInput = Extract<AppInput, { type: 'eval' }>
|
||||
export type UploadAppInput = Extract<AppInput, { type: 'upload' }>
|
||||
|
||||
export type AppInputs = Record<string, AppInput>
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
EvalAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UploadAppInput,
|
||||
UserAppInput
|
||||
} from './inputType'
|
||||
import type { World } from './rx'
|
||||
@@ -23,16 +24,25 @@ export type Aligned = {
|
||||
verticalAlignment: VerticalAlignment
|
||||
}
|
||||
|
||||
export interface GeneralAppInput {
|
||||
onlyStatic?: boolean
|
||||
evaluatedValue?: boolean
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
export interface BaseAppComponent extends Partial<Aligned> {
|
||||
id: ComponentID
|
||||
componentInput: AppInput | undefined
|
||||
configuration: Record<
|
||||
string,
|
||||
(StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput | EvalAppInput) & {
|
||||
onlyStatic?: boolean
|
||||
evaluatedValue?: boolean
|
||||
tooltip?: string
|
||||
}
|
||||
GeneralAppInput & (
|
||||
StaticAppInput
|
||||
| ConnectedAppInput
|
||||
| UserAppInput
|
||||
| RowAppInput
|
||||
| EvalAppInput
|
||||
| UploadAppInput
|
||||
)
|
||||
>
|
||||
card: boolean | undefined
|
||||
customCss?: Record<string, {
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
let c = ''
|
||||
export { c as class }
|
||||
export let accept = '*'
|
||||
export let multiple = true
|
||||
export let multiple = false
|
||||
export let convertToBase64 = false
|
||||
export let hideIcon = false
|
||||
export let iconSize = 36
|
||||
const dispatch = createEventDispatcher()
|
||||
let input: HTMLInputElement
|
||||
let files: File[] | undefined = undefined
|
||||
@@ -72,11 +73,15 @@
|
||||
duration-200 rounded-lg p-1 {c ?? ''}"
|
||||
>
|
||||
{#if !hideIcon && !files}
|
||||
<FileUp size={36} class="mb-2" />
|
||||
<FileUp size={iconSize} class="mb-2" />
|
||||
{/if}
|
||||
{#if files}
|
||||
<div class="w-full max-h-full overflow-auto px-6">
|
||||
<div class="text-center mb-2 px-2">Selected file{files.length > 1 ? 's' : ''}:</div>
|
||||
<slot name="selected-title">
|
||||
<div class="text-center mb-2 px-2">
|
||||
Selected file{files.length > 1 ? 's' : ''}:
|
||||
</div>
|
||||
</slot>
|
||||
<ul class="relative z-20 max-w-[250px] bg-white rounded-lg overflow-hidden mx-auto">
|
||||
{#each files as {name}, i}
|
||||
<li class="flex justify-between items-center font-normal text-sm
|
||||
@@ -97,7 +102,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<slot>
|
||||
<span>Drag and drop files</span>
|
||||
<span>Drag and drop {multiple ? 'files' : 'a file'}</span>
|
||||
</slot>
|
||||
{/if}
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user