Skip to content

Commit

Permalink
Merge pull request #10 from thisuxhq/add-drop-validation
Browse files Browse the repository at this point in the history
feat: add conditional check example and update dependencies to versio…
  • Loading branch information
Spikeysanju authored Dec 12, 2024
2 parents ae8cc2f + 7742efc commit e961643
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 68 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ interface DragDropAttributes {
- **[Nested Containers](https://github.com/thisuxhq/SvelteDnD/blob/main/src/routes/nested/+page.svelte)**: Explore the example in `src/routes/nested/+page.svelte`.
- **[Custom Classes](https://github.com/thisuxhq/SvelteDnD/blob/main/src/routes/custom-classes/+page.svelte)**: Explore the example in `src/routes/custom-classes/+page.svelte`.
- **[Interactive Elements](https://github.com/thisuxhq/SvelteDnD/blob/main/src/routes/interactive-elements/+page.svelte)**: Explore the example in `src/routes/interactive-elements/+page.svelte`.
- **[Conditional Check](https://github.com/thisuxhq/SvelteDnD/blob/main/src/routes/conditional-check/+page.svelte)**: Explore the example in `src/routes/conditional-check/+page.svelte`.

## Styling

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@thisux/sveltednd",
"version": "0.0.17",
"version": "0.0.18",
"private": false,
"description": "A lightweight, flexible drag and drop library for Svelte 5 applications.",
"author": "sanju <[email protected]>",
Expand Down Expand Up @@ -75,6 +75,6 @@
"vitest": "^2.0.4"
},
"dependencies": {
"@thisux/sveltednd": "^0.0.14"
"@thisux/sveltednd": "^0.0.17"
}
}
3 changes: 2 additions & 1 deletion src/lib/stores/dnd.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export const dndState = $state<DragDropState>({
draggedItem: null,
sourceContainer: '',
targetContainer: null,
targetElement: null
targetElement: null,
invalidDrop: false
});
1 change: 1 addition & 0 deletions src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface DragDropState<T = unknown> {
sourceContainer: string;
targetContainer: string | null;
targetElement: HTMLElement | null;
invalidDrop?: boolean;
}

export interface DragDropCallbacks<T = unknown> {
Expand Down
116 changes: 51 additions & 65 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,85 +12,71 @@
{ path: '/nested', title: 'Nested Containers' },
{ path: '/multiple', title: 'Multiple' },
{ path: '/custom-classes', title: 'Custom Classes' },
{ path: '/interactive-elements', title: 'Interactives' }
{ path: '/interactive-elements', title: 'Interactives' },
{ path: '/conditional-check', title: 'Conditional Check' }
];
const cn = (...classes: string[]) => classes.filter(Boolean).join(' ');
</script>

<div class="mx-auto min-h-screen w-full bg-gray-100">
<div class="flex flex-col">
<nav class="hidden border-b bg-white px-8 py-4 md:sticky md:top-0 md:block">
<div class="flex items-center justify-between">
<div class="flex gap-4 overflow-x-auto whitespace-nowrap">
<div class="flex-1 overflow-hidden">
<div class="flex gap-4 overflow-x-auto whitespace-nowrap">
{#each examples as { path, title }}
<a
href={path}
class={cn(
'rounded px-3 py-1 text-sm hover:bg-gray-100',
$page.url.pathname === path ? 'text-primary rounded-md bg-gray-100' : ''
)}
>
{title}
</a>
{/each}
</div>
</div>
</div>
<div class="flex gap-2">
<a
href="https://github.com/thisuxhq/sveltednd"
class="flex items-center gap-2 rounded-md bg-[#24292e] px-4 py-2 text-sm text-white hover:bg-[#1b1f23]"
target="_blank"
rel="noopener noreferrer"
<div class="flex min-h-screen bg-gray-100">
<!-- Sidebar -->
<aside class="hidden w-64 border-r bg-white md:block">
<div class="flex h-full flex-col">
<!-- Logo section -->
<div class="border-b p-4">
<div class="flex flex-col space-y-1">
<h1 class="text-primary text-xl font-semibold">SvelteDnD</h1>
<a href="https://thisux.com" target="_blank" class="hover:text-primary text-xs"
>by ThisUX</a
>
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
GitHub
</a>
</div>
</div>

<!-- Navigation links -->
<div class="flex-1 overflow-y-auto p-4">
{#each examples as { path, title }}
<a
href="https://www.npmjs.com/package/@thisux/sveltednd"
class="flex items-center gap-2 rounded-md bg-[#cb3837] px-4 py-2 text-sm text-white hover:bg-[#ab3231]"
target="_blank"
rel="noopener noreferrer"
href={path}
class={cn(
'block rounded px-3 py-2 text-sm font-medium transition-colors',
'hover:text-primary hover:bg-gray-100',
$page.url.pathname === path ? 'text-primary bg-gray-100' : 'text-gray-600'
)}
>
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path
d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669v-.001zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002v5.331zM10.665 10H12v2.667h-1.335V10z"
/>
</svg>
NPM
{title}
</a>
</div>
{/each}
</div>
</nav>
</div>
</aside>

<!-- Main content -->
<div class="flex-1">
{@render children()}
<nav class="sticky bottom-0 border-t bg-white px-8 py-4 md:hidden">
</div>
</div>

<!-- Mobile bottom navigation -->
<nav class="sticky bottom-0 border-t bg-white px-8 py-4 md:hidden">
<div class="flex gap-4 overflow-x-auto whitespace-nowrap">
<div class="flex-1 overflow-hidden">
<div class="flex gap-4 overflow-x-auto whitespace-nowrap">
<div class="flex-1 overflow-hidden">
<div class="flex gap-4 overflow-x-auto whitespace-nowrap">
{#each examples as { path, title }}
<a
href={path}
class={cn(
'rounded px-3 py-1 text-sm hover:bg-gray-100',
$page.url.pathname === path ? 'text-primary rounded-md bg-gray-100' : ''
)}
>
{title}
</a>
{/each}
</div>
</div>
{#each examples as { path, title }}
<a
href={path}
class={cn(
'rounded px-3 py-1 text-sm hover:bg-gray-100',
$page.url.pathname === path ? 'text-primary rounded-md bg-gray-100' : ''
)}
>
{title}
</a>
{/each}
</div>
</nav>
</div>
</div>
</div>
</nav>

<!-- Floating badge (hidden on mobile)-->
<footer class="fixed bottom-8 right-8 z-50 hidden md:block">
Expand Down
146 changes: 146 additions & 0 deletions src/routes/conditional-check/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<script lang="ts">
import { draggable, droppable } from '$lib/index.js';
import { dndState } from '$lib/stores/dnd.svelte.js';
import type { DragDropState } from '$lib/types/index.js';
interface Fruit {
id: string;
name: string;
color: string;
}
let sourceFruits = $state([
{ id: '1', name: 'Apple', color: 'Red' },
{ id: '2', name: 'Banana', color: 'Yellow' },
{ id: '3', name: 'Grapes', color: 'Green' },
{ id: '4', name: 'Orange', color: 'Orange' },
{ id: '5', name: 'Pineapple', color: 'Yellow' },
{ id: '6', name: 'Strawberry', color: 'Red' },
{ id: '7', name: 'Watermelon', color: 'Green' }
]);
let targetFruits = $state([]);
// Add a derived state for empty states
let isTargetEmpty = $derived(targetFruits.length === 0);
let isSourceEmpty = $derived(sourceFruits.length === 0);
// Validation function that sets invalidDrop state
function validateDrop(state: DragDropState<Fruit>) {
const fruit = state.draggedItem;
if (!fruit) return;
// Set invalidDrop based on the color condition
dndState.invalidDrop = fruit.color !== 'Red';
}
const dragDropCallbacks = {
onDragOver: (state: DragDropState<Fruit>) => {
validateDrop(state);
},
onDrop: async (state: DragDropState<Fruit>) => {
if (dndState.invalidDrop || !state.draggedItem) {
return; // Prevent invalid drops
}
// Move fruit to target container
sourceFruits = sourceFruits.filter((fruit) => fruit.id !== state.draggedItem.id);
targetFruits = [...targetFruits, state.draggedItem];
},
onDragEnd: () => {
// Reset invalidDrop state when drag ends
dndState.invalidDrop = false;
}
};
</script>

<div class="container mx-auto p-8">
<div class="mb-12 space-y-2">
<h1 class="text-3xl font-bold tracking-tight">Fruit Sorter</h1>
<p class="text-muted-foreground">
Drop the red fruits in the target zone. Other colors will be rejected.
</p>
</div>

<div class="grid gap-8 md:grid-cols-2">
<!-- Source Container -->
<div class="space-y-4">
<h2 class="text-sm font-medium uppercase tracking-wide text-muted-foreground">Available Fruits</h2>
<div
class="min-h-[400px] rounded-lg border bg-white p-4 shadow-sm transition-all"
use:droppable={{ container: 'source' }}
>
{#if isSourceEmpty}
<div class="flex h-full items-center justify-center">
<p class="text-muted-foreground">All fruits have been sorted</p>
</div>
{:else}
<div class="grid gap-2">
{#each sourceFruits as fruit}
<div
use:draggable={{ container: 'source', dragData: fruit }}
class={`group flex items-center justify-between rounded-md border p-3 shadow-sm transition-all hover:shadow
${fruit.color === 'Red' ? 'border-red-200 bg-red-50/50' : 'border-muted bg-muted/5'}`}
>
<span class="font-medium">{fruit.name}</span>
<span
class={`rounded px-2 py-1 text-xs
${fruit.color === 'Red' ? 'bg-red-100 text-red-700' : 'bg-muted/10 text-muted-foreground'}`}
>
{fruit.color}
</span>
</div>
{/each}
</div>
{/if}
</div>
</div>

<!-- Target Container -->
<div class="space-y-4">
<h2 class="text-sm font-medium uppercase tracking-wide text-muted-foreground">Red Fruits Only</h2>
<div
class={`min-h-[400px] rounded-lg border bg-white p-4 shadow-sm transition-all
${
dndState.isDragging
? dndState.invalidDrop
? 'border-red-500/50 bg-red-50/5'
: 'border-blue-500/50 bg-blue-50/5'
: ''
}`}

use:droppable={{
container: 'target',
callbacks: dragDropCallbacks,
attributes: {
dragOverClass: dndState.invalidDrop ? 'invalid-drop' : 'valid-drop'
}
}}
>
{#if isTargetEmpty}
<div class="flex h-full items-center justify-center">
<p class="text-muted-foreground">Drop red fruits here</p>
</div>
{:else}
<div class="grid gap-2">
{#each targetFruits as fruit}
<div class="flex items-center justify-between rounded-md border-red-200 bg-red-50/50 p-3 shadow-sm">
<span class="font-medium">{fruit.name}</span>
<span class="rounded bg-red-100 px-2 py-1 text-xs text-red-700">{fruit.color}</span>
</div>
{/each}
</div>
{/if}
</div>
</div>
</div>
</div>

<style>
.valid-drop {
@apply border-blue-500/50 bg-blue-50/5 ring-2 ring-blue-500/20 ring-offset-2;
}
.invalid-drop {
@apply border-red-500/50 bg-red-50/5 ring-2 ring-red-500/20 ring-offset-2;
}
</style>

0 comments on commit e961643

Please sign in to comment.