|
1 | 1 | <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"; |
2 | 6 | 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"; |
6 | 7 | import { |
7 | 8 | FileNode, |
8 | 9 | FileTree, |
|
19 | 20 | import { Toaster, toast } from "svelte-sonner"; |
20 | 21 | import { fade, fly } from "svelte/transition"; |
21 | 22 | import data from "./data.json" with { type: "json" }; |
22 | | - import { createDialogState } from "./state.svelte.js"; |
| 23 | + import { DialogState } from "./state.svelte.js"; |
23 | 24 |
|
24 | 25 | const tree = new FileTree({ |
25 | 26 | children: (tree) => |
|
41 | 42 | }), |
42 | 43 | }); |
43 | 44 |
|
44 | | - const { dialogData, openDialog, closeDialog, dialogOpen, onDialogOpenChange } = createDialogState< |
| 45 | + const dialog = new DialogState< |
45 | 46 | { |
46 | 47 | title: string; |
47 | 48 | description: string; |
|
75 | 76 | const description = `An item with the name "${target.name}" already exists`; |
76 | 77 | switch (operation) { |
77 | 78 | case "move": { |
78 | | - return openDialog({ |
| 79 | + return dialog.open({ |
79 | 80 | title: "Failed to move items", |
80 | 81 | description, |
81 | 82 | }); |
82 | 83 | } |
83 | 84 | case "insert": { |
84 | | - return openDialog({ |
| 85 | + return dialog.open({ |
85 | 86 | title: "Failed to paste items", |
86 | 87 | description, |
87 | 88 | }); |
|
97 | 98 | editable |
98 | 99 | draggable |
99 | 100 | 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", |
101 | 102 | dragged && "opacity-50", |
102 | 103 | dropPosition !== undefined && |
103 | 104 | "before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-[inherit] before:border-2", |
|
107 | 108 | ]} |
108 | 109 | style="margin-inline-start: {depth * 16}px;" |
109 | 110 | > |
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> |
121 | 137 |
|
122 | 138 | {#if editing} |
123 | 139 | <TreeItemInput class="border bg-white focus:outline-none" /> |
|
131 | 147 |
|
132 | 148 | <Toaster richColors /> |
133 | 149 |
|
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 | +> |
135 | 158 | <Dialog.Portal> |
136 | 159 | <Dialog.Overlay forceMount class="fixed inset-0 z-50 bg-black/50"> |
137 | 160 | {#snippet child({ props, open })} |
|
147 | 170 | > |
148 | 171 | {#snippet child({ props, open })} |
149 | 172 | {#if open} |
150 | | - {@const { title, description } = dialogData()!} |
151 | 173 | <div {...props} transition:fly={{ y: "-100%" }}> |
152 | 174 | <Dialog.Title class="text-center text-lg font-semibold tracking-tight"> |
153 | | - {title} |
| 175 | + {dialog.data?.title} |
154 | 176 | </Dialog.Title> |
155 | 177 |
|
156 | 178 | <Dialog.Description class="mt-2 text-sm text-gray-700"> |
157 | | - {description} |
| 179 | + {dialog.data?.description} |
158 | 180 | </Dialog.Description> |
159 | 181 |
|
160 | 182 | <div class="mt-5 flex justify-end gap-3"> |
161 | 183 | <button |
162 | 184 | 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")} |
164 | 186 | > |
165 | 187 | Skip |
166 | 188 | </button> |
167 | 189 |
|
168 | 190 | <button |
169 | 191 | 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")} |
171 | 193 | > |
172 | 194 | Cancel |
173 | 195 | </button> |
|
0 commit comments