Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit aceabc0

Browse files
author
Abdelrahman
authoredFeb 28, 2025··
Merge pull request #33 from abdel-17/improve-preview
Add Chevron Icon
2 parents 6ea0ce6 + 0b29396 commit aceabc0

File tree

6 files changed

+116
-85
lines changed

6 files changed

+116
-85
lines changed
 

‎pnpm-lock.yaml

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎sites/preview/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
"format:check": "prettier --check ."
1515
},
1616
"devDependencies": {
17+
"@lucide/svelte": "0.477.0-rc.0",
1718
"@sveltejs/adapter-static": "^3.0.6",
1819
"@sveltejs/kit": "^2.9.0",
1920
"@sveltejs/vite-plugin-svelte": "5.0.3",
2021
"@tailwindcss/vite": "4.0.5",
2122
"bits-ui": "1.0.0-next.90",
22-
"lucide-svelte": "^0.475.0",
2323
"prettier": "^3.5.0",
2424
"prettier-plugin-svelte": "^3.3.3",
2525
"prettier-plugin-tailwindcss": "^0.6.11",

‎sites/preview/src/routes/+page.svelte

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script lang="ts">
2+
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
3+
import FileIcon from "@lucide/svelte/icons/file";
4+
import FolderIcon from "@lucide/svelte/icons/folder";
5+
import FolderOpenIcon from "@lucide/svelte/icons/folder-open";
26
import { Dialog } from "bits-ui";
3-
import FileIcon from "lucide-svelte/icons/file";
4-
import FolderIcon from "lucide-svelte/icons/folder";
5-
import FolderOpenIcon from "lucide-svelte/icons/folder-open";
67
import {
78
FileNode,
89
FileTree,
@@ -19,7 +20,7 @@
1920
import { Toaster, toast } from "svelte-sonner";
2021
import { fade, fly } from "svelte/transition";
2122
import data from "./data.json" with { type: "json" };
22-
import { createDialogState } from "./state.svelte.js";
23+
import { DialogState } from "./state.svelte.js";
2324
2425
const tree = new FileTree({
2526
children: (tree) =>
@@ -41,7 +42,7 @@
4142
}),
4243
});
4344
44-
const { dialogData, openDialog, closeDialog, dialogOpen, onDialogOpenChange } = createDialogState<
45+
const dialog = new DialogState<
4546
{
4647
title: string;
4748
description: string;
@@ -75,13 +76,13 @@
7576
const description = `An item with the name "${target.name}" already exists`;
7677
switch (operation) {
7778
case "move": {
78-
return openDialog({
79+
return dialog.open({
7980
title: "Failed to move items",
8081
description,
8182
});
8283
}
8384
case "insert": {
84-
return openDialog({
85+
return dialog.open({
8586
title: "Failed to paste items",
8687
description,
8788
});
@@ -97,7 +98,7 @@
9798
editable
9899
draggable
99100
class={[
100-
"relative flex items-center gap-2 rounded-md border border-neutral-400 p-3 hover:bg-neutral-200 focus:outline-2 focus:outline-offset-2 focus:outline-current active:bg-neutral-300 aria-selected:border-blue-400 aria-selected:bg-blue-100 aria-selected:text-blue-800 aria-selected:active:bg-blue-200",
101+
"relative flex items-center rounded-md border border-neutral-400 p-3 hover:bg-neutral-200 focus:outline-2 focus:outline-offset-2 focus:outline-current active:bg-neutral-300 aria-selected:border-blue-400 aria-selected:bg-blue-100 aria-selected:text-blue-800 aria-selected:active:bg-blue-200",
101102
dragged && "opacity-50",
102103
dropPosition !== undefined &&
103104
"before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-[inherit] before:border-2",
@@ -107,17 +108,32 @@
107108
]}
108109
style="margin-inline-start: {depth * 16}px;"
109110
>
110-
{#if node.type === "file"}
111-
<FileIcon role="presentation" />
112-
{:else if node.expanded}
113-
<FolderOpenIcon
114-
role="presentation"
115-
class="fill-blue-300"
116-
onclick={() => node.collapse()}
117-
/>
118-
{:else}
119-
<FolderIcon role="presentation" class="fill-blue-300" onclick={() => node.expand()} />
120-
{/if}
111+
<ChevronDownIcon
112+
role="presentation"
113+
size={20}
114+
class={[
115+
"rounded-full p-0.25 transition-transform duration-200 hover:bg-current/8 active:bg-current/12",
116+
node.type === "folder" && node.expanded && "-rotate-90",
117+
node.type === "file" && "invisible",
118+
]}
119+
onclick={(event) => {
120+
if (node.type === "folder") {
121+
node.toggleExpanded();
122+
}
123+
124+
event.stopPropagation();
125+
}}
126+
/>
127+
128+
<div class="ms-1 me-3">
129+
{#if node.type === "file"}
130+
<FileIcon role="presentation" />
131+
{:else if node.expanded}
132+
<FolderOpenIcon role="presentation" class="fill-blue-300" />
133+
{:else}
134+
<FolderIcon role="presentation" class="fill-blue-300" />
135+
{/if}
136+
</div>
121137

122138
{#if editing}
123139
<TreeItemInput class="border bg-white focus:outline-none" />
@@ -131,7 +147,14 @@
131147

132148
<Toaster richColors />
133149

134-
<Dialog.Root bind:open={dialogOpen, onDialogOpenChange}>
150+
<Dialog.Root
151+
open={dialog.data !== undefined}
152+
onOpenChange={(open) => {
153+
if (!open) {
154+
dialog.close();
155+
}
156+
}}
157+
>
135158
<Dialog.Portal>
136159
<Dialog.Overlay forceMount class="fixed inset-0 z-50 bg-black/50">
137160
{#snippet child({ props, open })}
@@ -147,27 +170,26 @@
147170
>
148171
{#snippet child({ props, open })}
149172
{#if open}
150-
{@const { title, description } = dialogData()!}
151173
<div {...props} transition:fly={{ y: "-100%" }}>
152174
<Dialog.Title class="text-center text-lg font-semibold tracking-tight">
153-
{title}
175+
{dialog.data?.title}
154176
</Dialog.Title>
155177

156178
<Dialog.Description class="mt-2 text-sm text-gray-700">
157-
{description}
179+
{dialog.data?.description}
158180
</Dialog.Description>
159181

160182
<div class="mt-5 flex justify-end gap-3">
161183
<button
162184
class="rounded-md border-1 border-current bg-neutral-100 px-3 py-1.5 text-sm leading-5 font-semibold text-neutral-800 hover:bg-neutral-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current active:bg-neutral-300"
163-
onclick={() => closeDialog("skip")}
185+
onclick={() => dialog.close("skip")}
164186
>
165187
Skip
166188
</button>
167189

168190
<button
169191
class="rounded-md border-1 border-current bg-red-100 px-3 py-1.5 text-sm leading-5 font-semibold text-red-800 hover:bg-red-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current active:bg-red-300"
170-
onclick={() => closeDialog("cancel")}
192+
onclick={() => dialog.close("cancel")}
171193
>
172194
Cancel
173195
</button>

‎sites/preview/src/routes/state.svelte.ts

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,28 @@ export type DialogStateProps<TResult> = {
22
defaultResult: TResult;
33
};
44

5-
export const createDialogState = <TData, TResult>({ defaultResult }: DialogStateProps<TResult>) => {
6-
let dialogData: TData | undefined = $state.raw();
7-
let resolveOpenDialog: ((result: TResult) => void) | undefined;
5+
export class DialogState<TData, TResult> {
6+
readonly #defaultResult: TResult;
7+
#data?: TData = $state.raw();
8+
#resolveOpen?: (result: TResult) => void;
89

9-
const openDialog = (data: TData): Promise<TResult> => {
10-
dialogData = data;
11-
return new Promise((resolve) => {
12-
resolveOpenDialog = resolve;
13-
});
14-
};
15-
16-
const closeDialog = (result: TResult = defaultResult): void => {
17-
dialogData = undefined;
18-
resolveOpenDialog?.(result);
19-
};
10+
constructor(props: DialogStateProps<TResult>) {
11+
this.#defaultResult = props.defaultResult;
12+
}
2013

21-
const dialogOpen = (): boolean => dialogData !== undefined;
14+
get data(): TData | undefined {
15+
return this.#data;
16+
}
2217

23-
const onDialogOpenChange = (value: boolean): void => {
24-
if (!value) {
25-
closeDialog();
26-
}
27-
};
18+
open(data: TData): Promise<TResult> {
19+
this.#data = data;
20+
return new Promise((resolve) => {
21+
this.#resolveOpen = resolve;
22+
});
23+
}
2824

29-
return {
30-
dialogData: () => dialogData,
31-
openDialog,
32-
closeDialog,
33-
dialogOpen,
34-
onDialogOpenChange,
35-
};
36-
};
25+
close(result: TResult = this.#defaultResult): void {
26+
this.#data = undefined;
27+
this.#resolveOpen?.(result);
28+
}
29+
}

‎sites/sveltekit-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
"format:check": "prettier --check ."
1515
},
1616
"devDependencies": {
17+
"@lucide/svelte": "0.477.0-rc.0",
1718
"@sveltejs/adapter-auto": "^4.0.0",
1819
"@sveltejs/kit": "^2.16.0",
1920
"@sveltejs/vite-plugin-svelte": "^5.0.0",
2021
"@tailwindcss/vite": "^4.0.0",
2122
"@types/better-sqlite3": "^7.6.12",
2223
"better-sqlite3": "^11.8.1",
23-
"lucide-svelte": "^0.475.0",
2424
"prettier": "^3.4.2",
2525
"prettier-plugin-svelte": "^3.3.3",
2626
"prettier-plugin-tailwindcss": "^0.6.11",

‎sites/sveltekit-example/src/routes/+page.svelte

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script lang="ts">
22
import { invalidate } from "$app/navigation";
3-
import FileIcon from "lucide-svelte/icons/file";
4-
import FolderIcon from "lucide-svelte/icons/folder";
5-
import FolderOpenIcon from "lucide-svelte/icons/folder-open";
3+
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
4+
import FileIcon from "@lucide/svelte/icons/file";
5+
import FolderIcon from "@lucide/svelte/icons/folder";
6+
import FolderOpenIcon from "@lucide/svelte/icons/folder-open";
67
import { SvelteSet } from "svelte/reactivity";
78
import {
89
FileNode,
@@ -251,7 +252,7 @@
251252
draggable
252253
{disabled}
253254
class={[
254-
"relative flex items-center gap-2 rounded-md border border-neutral-400 p-3 hover:bg-neutral-200 focus:outline-2 focus:outline-offset-2 focus:outline-current active:bg-neutral-300 aria-selected:border-blue-400 aria-selected:bg-blue-100 aria-selected:text-blue-800 aria-selected:active:bg-blue-200",
255+
"relative flex items-center rounded-md border border-neutral-400 p-3 hover:bg-neutral-200 focus:outline-2 focus:outline-offset-2 focus:outline-current active:bg-neutral-300 aria-selected:border-blue-400 aria-selected:bg-blue-100 aria-selected:text-blue-800 aria-selected:active:bg-blue-200",
255256
dragged && "opacity-50",
256257
dropPosition !== undefined &&
257258
"before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-[inherit] before:border-2",
@@ -262,17 +263,32 @@
262263
]}
263264
style="margin-inline-start: {depth * 16}px;"
264265
>
265-
{#if node.type === "file"}
266-
<FileIcon role="presentation" />
267-
{:else if node.expanded}
268-
<FolderOpenIcon
269-
role="presentation"
270-
class="fill-blue-300"
271-
onclick={() => node.collapse()}
272-
/>
273-
{:else}
274-
<FolderIcon role="presentation" class="fill-blue-300" onclick={() => node.expand()} />
275-
{/if}
266+
<ChevronDownIcon
267+
role="presentation"
268+
size={20}
269+
class={[
270+
"rounded-full p-0.25 transition-transform duration-200 hover:bg-current/8 active:bg-current/12",
271+
node.type === "folder" && node.expanded && "-rotate-90",
272+
node.type === "file" && "invisible",
273+
]}
274+
onclick={(event) => {
275+
if (node.type === "folder") {
276+
node.toggleExpanded();
277+
}
278+
279+
event.stopPropagation();
280+
}}
281+
/>
282+
283+
<div class="ms-1 me-3">
284+
{#if node.type === "file"}
285+
<FileIcon role="presentation" />
286+
{:else if node.expanded}
287+
<FolderOpenIcon role="presentation" class="fill-blue-300" />
288+
{:else}
289+
<FolderIcon role="presentation" class="fill-blue-300" />
290+
{/if}
291+
</div>
276292

277293
{#if editing}
278294
<TreeItemInput class="border bg-white focus:outline-none" />

0 commit comments

Comments
 (0)
Please sign in to comment.