Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add conditional check example and update dependencies to versio… #10

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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 <work@sanju.sh>",
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>
Loading