Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions apps/v4/components/demo/DropzoneDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<script setup lang="ts">
import {
Dropzone,
DropzoneArea,
DropzoneDescription,
DropzoneFileList,
DropzoneFileListItem,
DropzoneFileMessage,
DropzoneMessage,
DropzoneRemoveFile,
DropzoneRetryFile,
DropzoneTrigger,
InfiniteProgress,
useDropzoneUpload,
} from '@/registry/new-york-v4/ui/dropzone'

const dropzone = useDropzoneUpload({
onDropFile: async () => {
await new Promise(resolve =>
setTimeout(resolve, Math.random() * 300 + 800),
)

if (Math.random() > 0.75) {
return {
status: 'error' as const,
error: 'Upload failed',
}
}
return {
status: 'success' as const,
result: undefined,
}
},
validation: {
maxFiles: 1,
maxSize: 1 * 1024 * 1024, // 1MB
},
})
</script>

<template>
<div class="w-full max-w-md mx-auto">
<Dropzone v-bind="dropzone" class="space-y-4">
<div class="flex flex-col sm:flex-row sm:justify-between gap-2">
<DropzoneDescription class="text-sm">
Select up to 1 file (max 1MB)
</DropzoneDescription>
<DropzoneMessage class="text-sm" />
</div>

<DropzoneArea>
<DropzoneTrigger
class="flex flex-col items-center gap-3 rounded-lg border-2 border-dashed border-muted-foreground/25 p-6 text-center hover:border-muted-foreground/50 transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-muted-foreground"
>
<path d="M12 13v8" />
<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
<path d="m8 17 4-4 4 4" />
</svg>
<div class="text-sm">
<p class="font-medium">
Upload file
</p>
<p class="text-muted-foreground">
Click or drag and drop
</p>
</div>
</DropzoneTrigger>
</DropzoneArea>

<div class="overflow-clip rounded-md mt-4">
<DropzoneFileList
v-if="dropzone.fileStatuses.value.length > 0"
class="space-y-2 max-h-48 pr-1 overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:bg-muted-foreground/20 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-track]:bg-muted-foreground/20"
>
<DropzoneFileListItem
v-for="file in dropzone.fileStatuses.value" :key="file.id" :file="file"
class="rounded-md border p-3"
>
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="text-muted-foreground shrink-0"
>
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
</svg>
<div class="min-w-0">
<p class="text-sm font-medium truncate">
{{ file.fileName }}
</p>
<p class="text-xs text-muted-foreground">
{{ (file.file.size / (1024 * 1024)).toFixed(2) }} MB
</p>
</div>
</div>
<div class="flex items-center gap-1">
<DropzoneRetryFile
v-if="file.status === 'error'" variant="ghost" size="sm"
class="p-1 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
<path d="M8 16H3v5" />
</svg>
</DropzoneRetryFile>
<DropzoneRemoveFile variant="ghost" size="sm" class="p-1 cursor-pointer">
<svg
xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
</DropzoneRemoveFile>
</div>
</div>
<InfiniteProgress :status="file.status" class="mt-2" />
<DropzoneFileMessage class="text-xs text-right mt-1" />
</DropzoneFileListItem>
</DropzoneFileList>
</div>
</Dropzone>
</div>
</template>
133 changes: 133 additions & 0 deletions apps/v4/components/demo/DropzoneMultiFile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<script setup lang="ts">
import {
Dropzone,
DropzoneArea,
DropzoneDescription,
DropzoneFileList,
DropzoneFileListItem,
DropzoneFileMessage,
DropzoneMessage,
DropzoneRemoveFile,
DropzoneRetryFile,
DropzoneTrigger,
InfiniteProgress,
useDropzoneUpload,
} from '@/registry/new-york-v4/ui/dropzone'

const dropzone = useDropzoneUpload({
onDropFile: async () => {
await new Promise(resolve => setTimeout(resolve, 1000))

// Deterministic failure for demo purposes
if (Math.random() > 0.75) {
return {
status: 'error' as const,
error: 'Upload failed',
}
}
return {
status: 'success' as const,
result: undefined,
}
},
validation: {
maxFiles: 4,
maxSize: 5 * 1024 * 1024, // 5MB
},
})
</script>

<template>
<div class="w-full max-w-md mx-auto">
<Dropzone v-bind="dropzone" class="space-y-4">
<div class="flex flex-col sm:flex-row sm:justify-between gap-2">
<DropzoneDescription class="text-sm">
Select up to 4 files (max 5MB)
</DropzoneDescription>
<DropzoneMessage class="text-sm" />
</div>

<DropzoneArea>
<DropzoneTrigger
class="flex flex-col items-center gap-3 rounded-lg border-2 border-dashed border-muted-foreground/25 p-6 text-center hover:border-muted-foreground/50 transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-muted-foreground"
>
<path d="M12 13v8" />
<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
<path d="m8 17 4-4 4 4" />
</svg>
<div class="text-sm">
<p class="font-medium">
Upload files
</p>
<p class="text-muted-foreground">
Click or drag and drop
</p>
</div>
</DropzoneTrigger>
</DropzoneArea>

<div class="overflow-clip rounded-md mt-4">
<DropzoneFileList
v-if="dropzone.fileStatuses.value.length > 0"
class="space-y-2 max-h-48 pr-1 overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:bg-muted-foreground/20 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-track]:bg-muted-foreground/20"
>
<DropzoneFileListItem
v-for="file in dropzone.fileStatuses.value" :key="file.id" :file="file"
class="rounded-md border p-3"
>
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-muted-foreground shrink-0"
>
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
</svg>
<div class="min-w-0">
<p class="text-sm font-medium truncate">
{{ file.fileName }}
</p>
<p class="text-xs text-muted-foreground">
{{ (file.file.size / (1024 * 1024)).toFixed(2) }} MB
</p>
</div>
</div>
<div class="flex items-center gap-1">
<DropzoneRetryFile v-if="file.status === 'error'" variant="ghost" size="sm" class="p-1 cursor-pointer">
<svg
xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
>
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
<path d="M8 16H3v5" />
</svg>
</DropzoneRetryFile>
<DropzoneRemoveFile variant="ghost" size="sm" class="p-1 cursor-pointer">
<svg
xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
</DropzoneRemoveFile>
</div>
</div>
<InfiniteProgress :status="file.status" class="mt-2" />
<DropzoneFileMessage class="text-xs text-right mt-1" />
</DropzoneFileListItem>
</DropzoneFileList>
</div>
</Dropzone>
</div>
</template>
121 changes: 121 additions & 0 deletions apps/v4/components/demo/DropzoneMultiImage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts">
import { onBeforeUnmount } from 'vue'
import {
Dropzone,
DropzoneArea,
DropzoneDescription,
DropzoneFileList,
DropzoneFileListItem,
DropzoneMessage,
DropzoneRemoveFile,
DropzoneTrigger,
useDropzoneUpload,
} from '@/registry/new-york-v4/ui/dropzone'

const createdUrls = new Set<string>()

const dropzone = useDropzoneUpload({
onDropFile: async (file: File) => {
await new Promise(resolve => setTimeout(resolve, 800))
const url = URL.createObjectURL(file)
createdUrls.add(url)
return {
status: 'success' as const,
result: url,
}
},
onRemoveFile: async (id: string) => {
const file = dropzone.fileStatuses.value.find(f => f.id === id)
if (file?.result && typeof file.result === 'string' && createdUrls.has(file.result)) {
URL.revokeObjectURL(file.result)
createdUrls.delete(file.result)
}
},
validation: {
accept: ['image/png', 'image/jpeg', 'image/jpg'],
maxSize: 5 * 1024 * 1024, // 5MB
maxFiles: 4,
},
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Clean up blob URLs on unmount
onBeforeUnmount(() => {
for (const url of createdUrls) {
URL.revokeObjectURL(url)
}
createdUrls.clear()
})
</script>

<template>
<div class="w-full max-w-lg mx-auto">
<Dropzone v-bind="dropzone" class="space-y-4">
<div class="flex flex-col sm:flex-row sm:justify-between gap-2">
<DropzoneDescription class="text-sm">
Select up to 4 images (max 5MB)
</DropzoneDescription>
<DropzoneMessage class="text-sm" />
</div>

<DropzoneArea>
<DropzoneTrigger
class="flex flex-col items-center gap-3 rounded-lg border-2 border-dashed border-muted-foreground/25 p-6 text-center hover:border-muted-foreground/50 transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-muted-foreground"
>
<path d="M12 13v8" />
<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
<path d="m8 17 4-4 4 4" />
</svg>
<div class="text-sm">
<p class="font-medium">
Upload images
</p>
<p class="text-muted-foreground">
Click or drag and drop
</p>
</div>
</DropzoneTrigger>
</DropzoneArea>

<div class="overflow-clip rounded-md mt-4">
<DropzoneFileList
v-if="dropzone.fileStatuses.value.length > 0"
class="grid gap-1.5 grid-cols-4 pr-1 max-h-48 overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:bg-muted-foreground/20 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-track]:bg-muted-foreground/20"
>
<DropzoneFileListItem
v-for="file in dropzone.fileStatuses.value" :key="file.id" :file="file"
class="overflow-hidden rounded bg-secondary shadow-sm group relative"
>
<div class="relative">
<div v-if="file.status === 'pending'" class="aspect-square animate-pulse bg-black/20" />
<img
v-if="file.status === 'success'" :src="file.result" :alt="`uploaded-${file.fileName}`"
class="aspect-square object-cover w-full"
>
<DropzoneRemoveFile variant="ghost" size="sm" class="absolute cursor-pointer top-0.5 right-0.5 p-0.5 opacity-0 group-hover:opacity-100 focus:opacity-100 focus-visible:opacity-100 focus-within:opacity-100 sm:opacity-100 transition-opacity bg-black/50 hover:bg-black/70">
<svg
xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-white"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
</DropzoneRemoveFile>
</div>
<div class="p-1">
<p class="truncate text-xs leading-tight">
{{ file.fileName }}
</p>
</div>
</DropzoneFileListItem>
</DropzoneFileList>
</div>
</Dropzone>
</div>
</template>
Loading