plasma-svelte 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +65 -0
  2. package/dist/accordion/Accordion.svelte +28 -0
  3. package/dist/accordion/Accordion.svelte.d.ts +8 -0
  4. package/dist/accordion/AccordionItem.svelte +193 -0
  5. package/dist/accordion/AccordionItem.svelte.d.ts +14 -0
  6. package/dist/avatar/Avatar.svelte +178 -0
  7. package/dist/avatar/Avatar.svelte.d.ts +16 -0
  8. package/dist/badge/Badge.svelte +162 -0
  9. package/dist/badge/Badge.svelte.d.ts +16 -0
  10. package/dist/breadcrumb/Breadcrumb.svelte +59 -0
  11. package/dist/breadcrumb/Breadcrumb.svelte.d.ts +9 -0
  12. package/dist/breadcrumb/BreadcrumbItem.svelte +151 -0
  13. package/dist/breadcrumb/BreadcrumbItem.svelte.d.ts +14 -0
  14. package/dist/button/Button.svelte +274 -0
  15. package/dist/button/Button.svelte.d.ts +22 -0
  16. package/dist/button/ButtonGroup.svelte +98 -0
  17. package/dist/button/ButtonGroup.svelte.d.ts +12 -0
  18. package/dist/card/Card.svelte +191 -0
  19. package/dist/card/Card.svelte.d.ts +18 -0
  20. package/dist/checkbox/Checkbox.svelte +199 -0
  21. package/dist/checkbox/Checkbox.svelte.d.ts +12 -0
  22. package/dist/datepicker/DatePicker.svelte +509 -0
  23. package/dist/datepicker/DatePicker.svelte.d.ts +14 -0
  24. package/dist/divider/Divider.svelte +112 -0
  25. package/dist/divider/Divider.svelte.d.ts +13 -0
  26. package/dist/drawer/Drawer.svelte +330 -0
  27. package/dist/drawer/Drawer.svelte.d.ts +21 -0
  28. package/dist/dropzone/DropZone.svelte +282 -0
  29. package/dist/dropzone/DropZone.svelte.d.ts +17 -0
  30. package/dist/emptystate/EmptyState.svelte +169 -0
  31. package/dist/emptystate/EmptyState.svelte.d.ts +15 -0
  32. package/dist/headerbar/HeaderBar.svelte +210 -0
  33. package/dist/headerbar/HeaderBar.svelte.d.ts +17 -0
  34. package/dist/index.d.ts +44 -0
  35. package/dist/index.js +44 -0
  36. package/dist/input/Input.svelte +227 -0
  37. package/dist/input/Input.svelte.d.ts +14 -0
  38. package/dist/layout/Flex.svelte +110 -0
  39. package/dist/layout/Flex.svelte.d.ts +20 -0
  40. package/dist/layout/Grid.svelte +78 -0
  41. package/dist/layout/Grid.svelte.d.ts +19 -0
  42. package/dist/layout/Space.svelte +64 -0
  43. package/dist/layout/Space.svelte.d.ts +16 -0
  44. package/dist/layout/utils.d.ts +2 -0
  45. package/dist/layout/utils.js +19 -0
  46. package/dist/menu/Menu.svelte +156 -0
  47. package/dist/menu/Menu.svelte.d.ts +14 -0
  48. package/dist/menu/MenuDivider.svelte +19 -0
  49. package/dist/menu/MenuDivider.svelte.d.ts +6 -0
  50. package/dist/menu/MenuItem.svelte +126 -0
  51. package/dist/menu/MenuItem.svelte.d.ts +13 -0
  52. package/dist/message/InlineMessage.svelte +226 -0
  53. package/dist/message/InlineMessage.svelte.d.ts +15 -0
  54. package/dist/modal/ConfirmDialog.svelte +151 -0
  55. package/dist/modal/ConfirmDialog.svelte.d.ts +19 -0
  56. package/dist/modal/Modal.svelte +279 -0
  57. package/dist/modal/Modal.svelte.d.ts +18 -0
  58. package/dist/pagination/Pagination.svelte +258 -0
  59. package/dist/pagination/Pagination.svelte.d.ts +16 -0
  60. package/dist/progress/ProgressBar.svelte +179 -0
  61. package/dist/progress/ProgressBar.svelte.d.ts +15 -0
  62. package/dist/radio/Radio.svelte +161 -0
  63. package/dist/radio/Radio.svelte.d.ts +13 -0
  64. package/dist/searchfield/SearchField.svelte +246 -0
  65. package/dist/searchfield/SearchField.svelte.d.ts +15 -0
  66. package/dist/select/Select.svelte +392 -0
  67. package/dist/select/Select.svelte.d.ts +20 -0
  68. package/dist/sidebar/Sidebar.svelte +78 -0
  69. package/dist/sidebar/Sidebar.svelte.d.ts +11 -0
  70. package/dist/sidebar/SidebarGroup.svelte +51 -0
  71. package/dist/sidebar/SidebarGroup.svelte.d.ts +9 -0
  72. package/dist/sidebar/SidebarItem.svelte +185 -0
  73. package/dist/sidebar/SidebarItem.svelte.d.ts +16 -0
  74. package/dist/slider/Slider.svelte +204 -0
  75. package/dist/slider/Slider.svelte.d.ts +16 -0
  76. package/dist/spinbox/SpinBox.svelte +299 -0
  77. package/dist/spinbox/SpinBox.svelte.d.ts +20 -0
  78. package/dist/spinner/Spinner.svelte +130 -0
  79. package/dist/spinner/Spinner.svelte.d.ts +11 -0
  80. package/dist/style/base.css +178 -0
  81. package/dist/style/index.css +2 -0
  82. package/dist/style/token.css +230 -0
  83. package/dist/switch/Switch.svelte +199 -0
  84. package/dist/switch/Switch.svelte.d.ts +13 -0
  85. package/dist/table/Table.svelte +305 -0
  86. package/dist/table/Table.svelte.d.ts +32 -0
  87. package/dist/tabs/Tabs.svelte +216 -0
  88. package/dist/tabs/Tabs.svelte.d.ts +21 -0
  89. package/dist/textarea/TextArea.svelte +114 -0
  90. package/dist/textarea/TextArea.svelte.d.ts +14 -0
  91. package/dist/timepicker/TimePicker.svelte +452 -0
  92. package/dist/timepicker/TimePicker.svelte.d.ts +16 -0
  93. package/dist/tooltip/Tooltip.svelte +122 -0
  94. package/dist/tooltip/Tooltip.svelte.d.ts +12 -0
  95. package/package.json +66 -0
@@ -0,0 +1,330 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ type DrawerPlacement = "left" | "right" | "top" | "bottom";
6
+
7
+ interface Props extends HTMLAttributes<HTMLDivElement> {
8
+ open?: boolean;
9
+ inline?: boolean;
10
+ placement?: DrawerPlacement;
11
+ title?: string;
12
+ subtitle?: string;
13
+ width?: string;
14
+ height?: string;
15
+ closeOnEsc?: boolean;
16
+ closeOnBackdrop?: boolean;
17
+ header?: Snippet;
18
+ footer?: Snippet;
19
+ children?: Snippet;
20
+ onclose?: () => void;
21
+ }
22
+
23
+ let {
24
+ open = $bindable(false),
25
+ inline = false,
26
+ placement = "right",
27
+ title = "",
28
+ subtitle = "",
29
+ width = "360px",
30
+ height = "320px",
31
+ closeOnEsc = true,
32
+ closeOnBackdrop = true,
33
+ header,
34
+ footer,
35
+ children,
36
+ onclose,
37
+ class: customClass = "",
38
+ style = "",
39
+ ...restProps
40
+ }: Props = $props();
41
+
42
+ function close() {
43
+ open = false;
44
+ onclose?.();
45
+ }
46
+
47
+ function handleBackdropClick(e: MouseEvent) {
48
+ if (!inline && closeOnBackdrop && e.target === e.currentTarget) {
49
+ close();
50
+ }
51
+ }
52
+
53
+ function handleKeydown(e: KeyboardEvent) {
54
+ if (open && !inline && closeOnEsc && e.key === "Escape") {
55
+ e.stopPropagation();
56
+ close();
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <svelte:window onkeydown={handleKeydown} />
62
+
63
+ {#if open || inline}
64
+ <div
65
+ class="plasma-drawer-backdrop"
66
+ class:plasma-drawer-backdrop--inline={inline}
67
+ onclick={handleBackdropClick}
68
+ role="presentation"
69
+ >
70
+ <div
71
+ role="dialog"
72
+ aria-modal={!inline}
73
+ aria-labelledby={title ? "plasma-drawer-title" : undefined}
74
+ class="plasma-drawer plasma-drawer--{placement} {customClass}"
75
+ class:plasma-drawer--inline={inline}
76
+ style="{placement === 'left' || placement === 'right' ? `width: ${width};` : `height: ${height};`} {style}"
77
+ {...restProps}
78
+ >
79
+ <!-- Header -->
80
+ <div class="plasma-drawer-header">
81
+ {#if header}
82
+ {@render header()}
83
+ {:else}
84
+ <div class="plasma-drawer-titles">
85
+ {#if title}
86
+ <h2 id="plasma-drawer-title" class="plasma-drawer-title">{title}</h2>
87
+ {/if}
88
+ {#if subtitle}
89
+ <span class="plasma-drawer-subtitle">{subtitle}</span>
90
+ {/if}
91
+ </div>
92
+ {/if}
93
+
94
+ {#if !inline}
95
+ <button
96
+ type="button"
97
+ class="plasma-drawer-close-btn"
98
+ onclick={close}
99
+ aria-label="Close drawer"
100
+ >
101
+ <svg
102
+ aria-hidden="true"
103
+ viewBox="0 0 16 16"
104
+ width="12"
105
+ height="12"
106
+ fill="currentColor"
107
+ >
108
+ <path
109
+ d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
110
+ />
111
+ </svg>
112
+ </button>
113
+ {/if}
114
+ </div>
115
+
116
+ <!-- Body -->
117
+ {#if children}
118
+ <div class="plasma-drawer-body">
119
+ {@render children()}
120
+ </div>
121
+ {/if}
122
+
123
+ <!-- Footer -->
124
+ {#if footer}
125
+ <div class="plasma-drawer-footer">
126
+ {@render footer()}
127
+ </div>
128
+ {/if}
129
+ </div>
130
+ </div>
131
+ {/if}
132
+
133
+ <style>
134
+ /* --------------------------------------------------------------------------
135
+ Backdrop Overlay
136
+ -------------------------------------------------------------------------- */
137
+ .plasma-drawer-backdrop {
138
+ position: fixed;
139
+ top: 0;
140
+ left: 0;
141
+ right: 0;
142
+ bottom: 0;
143
+ z-index: 9998;
144
+ background-color: rgba(0, 0, 0, 0.45);
145
+ backdrop-filter: blur(4px);
146
+ -webkit-backdrop-filter: blur(4px);
147
+ animation: plasma-drawer-fade var(--plasma-duration-base) var(--plasma-ease-default);
148
+ }
149
+
150
+ .plasma-drawer-backdrop--inline {
151
+ position: relative;
152
+ top: auto;
153
+ left: auto;
154
+ right: auto;
155
+ bottom: auto;
156
+ z-index: auto;
157
+ width: 100%;
158
+ min-height: 100%;
159
+ background-color: transparent;
160
+ backdrop-filter: none;
161
+ -webkit-backdrop-filter: none;
162
+ animation: none;
163
+ display: flex;
164
+ justify-content: center;
165
+ }
166
+
167
+ /* --------------------------------------------------------------------------
168
+ Drawer Sheet Panel (Kirigami.OverlaySheet)
169
+ -------------------------------------------------------------------------- */
170
+ .plasma-drawer {
171
+ position: fixed;
172
+ z-index: 9999;
173
+ display: flex;
174
+ flex-direction: column;
175
+ background-color: var(--plasma-color-surface);
176
+ color: var(--plasma-color-text);
177
+ font-family: var(--plasma-font-sans);
178
+ box-shadow: var(--plasma-shadow-lg);
179
+ box-sizing: border-box;
180
+ overflow: hidden;
181
+ }
182
+
183
+ .plasma-drawer--inline {
184
+ position: relative;
185
+ top: auto;
186
+ left: auto;
187
+ right: auto;
188
+ bottom: auto;
189
+ z-index: auto;
190
+ border: 1px solid var(--plasma-color-border);
191
+ border-radius: var(--plasma-radius-lg);
192
+ max-height: 480px;
193
+ animation: none !important;
194
+ }
195
+
196
+ /* Placements */
197
+ .plasma-drawer--right:not(.plasma-drawer--inline) {
198
+ top: 0;
199
+ right: 0;
200
+ bottom: 0;
201
+ border-left: 1px solid var(--plasma-color-border);
202
+ animation: plasma-slide-left var(--plasma-duration-base) var(--plasma-ease-default);
203
+ }
204
+
205
+ .plasma-drawer--left:not(.plasma-drawer--inline) {
206
+ top: 0;
207
+ left: 0;
208
+ bottom: 0;
209
+ border-right: 1px solid var(--plasma-color-border);
210
+ animation: plasma-slide-right var(--plasma-duration-base) var(--plasma-ease-default);
211
+ }
212
+
213
+ .plasma-drawer--bottom:not(.plasma-drawer--inline) {
214
+ bottom: 0;
215
+ left: 0;
216
+ right: 0;
217
+ border-top: 1px solid var(--plasma-color-border);
218
+ border-top-left-radius: var(--plasma-radius-xl);
219
+ border-top-right-radius: var(--plasma-radius-xl);
220
+ animation: plasma-slide-up var(--plasma-duration-base) var(--plasma-ease-default);
221
+ }
222
+
223
+ .plasma-drawer--top:not(.plasma-drawer--inline) {
224
+ top: 0;
225
+ left: 0;
226
+ right: 0;
227
+ border-bottom: 1px solid var(--plasma-color-border);
228
+ border-bottom-left-radius: var(--plasma-radius-xl);
229
+ border-bottom-right-radius: var(--plasma-radius-xl);
230
+ animation: plasma-slide-down var(--plasma-duration-base) var(--plasma-ease-default);
231
+ }
232
+
233
+ /* --------------------------------------------------------------------------
234
+ Header, Body, Footer
235
+ -------------------------------------------------------------------------- */
236
+ .plasma-drawer-header {
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: space-between;
240
+ padding: var(--plasma-space-md) var(--plasma-space-lg);
241
+ border-bottom: 1px solid var(--plasma-color-border-subtle);
242
+ background-color: var(--plasma-color-surface);
243
+ flex-shrink: 0;
244
+ }
245
+
246
+ .plasma-drawer-titles {
247
+ display: flex;
248
+ flex-direction: column;
249
+ }
250
+
251
+ .plasma-drawer-title {
252
+ margin: 0;
253
+ font-size: var(--plasma-font-size-md);
254
+ font-weight: var(--plasma-font-weight-bold);
255
+ color: var(--plasma-color-text);
256
+ }
257
+
258
+ .plasma-drawer-subtitle {
259
+ font-size: 11px;
260
+ color: var(--plasma-color-text-muted);
261
+ }
262
+
263
+ .plasma-drawer-close-btn {
264
+ display: inline-flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ width: 24px;
268
+ height: 24px;
269
+ border-radius: var(--plasma-radius-full);
270
+ border: none;
271
+ background: transparent;
272
+ color: var(--plasma-color-text-muted);
273
+ cursor: pointer;
274
+ transition: var(--plasma-transition-fast);
275
+ }
276
+
277
+ .plasma-drawer-close-btn:hover {
278
+ color: #ffffff;
279
+ background-color: var(--plasma-color-negative);
280
+ }
281
+
282
+ .plasma-drawer-body {
283
+ flex: 1;
284
+ overflow-y: auto;
285
+ padding: var(--plasma-space-lg);
286
+ font-size: var(--plasma-font-size-base);
287
+ line-height: var(--plasma-line-height-base);
288
+ scrollbar-width: thin;
289
+ scrollbar-color: var(--plasma-color-border) transparent;
290
+ }
291
+
292
+ .plasma-drawer-footer {
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: flex-end;
296
+ gap: var(--plasma-space-sm);
297
+ padding: var(--plasma-space-md) var(--plasma-space-lg);
298
+ border-top: 1px solid var(--plasma-color-border-subtle);
299
+ background-color: var(--plasma-color-surface-sunken);
300
+ flex-shrink: 0;
301
+ }
302
+
303
+ /* --------------------------------------------------------------------------
304
+ Slide Animations
305
+ -------------------------------------------------------------------------- */
306
+ @keyframes plasma-drawer-fade {
307
+ from { opacity: 0; }
308
+ to { opacity: 1; }
309
+ }
310
+
311
+ @keyframes plasma-slide-left {
312
+ from { transform: translateX(100%); }
313
+ to { transform: translateX(0); }
314
+ }
315
+
316
+ @keyframes plasma-slide-right {
317
+ from { transform: translateX(-100%); }
318
+ to { transform: translateX(0); }
319
+ }
320
+
321
+ @keyframes plasma-slide-up {
322
+ from { transform: translateY(100%); }
323
+ to { transform: translateY(0); }
324
+ }
325
+
326
+ @keyframes plasma-slide-down {
327
+ from { transform: translateY(-100%); }
328
+ to { transform: translateY(0); }
329
+ }
330
+ </style>
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ type DrawerPlacement = "left" | "right" | "top" | "bottom";
4
+ interface Props extends HTMLAttributes<HTMLDivElement> {
5
+ open?: boolean;
6
+ inline?: boolean;
7
+ placement?: DrawerPlacement;
8
+ title?: string;
9
+ subtitle?: string;
10
+ width?: string;
11
+ height?: string;
12
+ closeOnEsc?: boolean;
13
+ closeOnBackdrop?: boolean;
14
+ header?: Snippet;
15
+ footer?: Snippet;
16
+ children?: Snippet;
17
+ onclose?: () => void;
18
+ }
19
+ declare const Drawer: import("svelte").Component<Props, {}, "open">;
20
+ type Drawer = ReturnType<typeof Drawer>;
21
+ export default Drawer;
@@ -0,0 +1,282 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "onerror"> {
6
+ accept?: string;
7
+ multiple?: boolean;
8
+ disabled?: boolean;
9
+ loading?: boolean;
10
+ title?: string;
11
+ subtitle?: string;
12
+ icon?: Snippet;
13
+ children?: Snippet;
14
+ onfiles?: (files: File[]) => void;
15
+ onerror?: (message: string) => void;
16
+ }
17
+
18
+ let {
19
+ accept = "*",
20
+ multiple = false,
21
+ disabled = false,
22
+ loading = false,
23
+ title = "Drop files here, or browse",
24
+ subtitle = "Supports file uploads",
25
+ icon,
26
+ children,
27
+ onfiles,
28
+ onerror,
29
+ class: customClass = "",
30
+ ...restProps
31
+ }: Props = $props();
32
+
33
+ let isDragOver = $state(false);
34
+ let fileInput = $state<HTMLInputElement | null>(null);
35
+
36
+ function isAccepted(file: File): boolean {
37
+ if (!accept || accept === "*") return true;
38
+ const acceptedTypes = accept.split(",").map((t) => t.trim().toLowerCase());
39
+ const fileName = file.name.toLowerCase();
40
+ const fileType = file.type.toLowerCase();
41
+
42
+ return acceptedTypes.some((pattern) => {
43
+ if (pattern.startsWith(".")) {
44
+ return fileName.endsWith(pattern);
45
+ }
46
+ if (pattern.endsWith("/*")) {
47
+ const baseType = pattern.replace("/*", "");
48
+ return fileType.startsWith(baseType);
49
+ }
50
+ return fileType === pattern;
51
+ });
52
+ }
53
+
54
+ function handleFiles(fileList: FileList | null) {
55
+ if (!fileList || fileList.length === 0 || disabled || loading) return;
56
+
57
+ const filesArray = Array.from(fileList);
58
+ const validFiles: File[] = [];
59
+ const invalidFiles: File[] = [];
60
+
61
+ for (const f of filesArray) {
62
+ if (isAccepted(f)) {
63
+ validFiles.push(f);
64
+ } else {
65
+ invalidFiles.push(f);
66
+ }
67
+ }
68
+
69
+ if (invalidFiles.length > 0) {
70
+ const msg = `Unsupported file type: ${invalidFiles.map((f) => f.name).join(", ")}. Accepted: ${accept}`;
71
+ onerror?.(msg);
72
+ }
73
+
74
+ if (validFiles.length > 0) {
75
+ onfiles?.(multiple ? validFiles : [validFiles[0]]);
76
+ }
77
+
78
+ if (fileInput) {
79
+ fileInput.value = "";
80
+ }
81
+ }
82
+
83
+ function handleDragOver(e: DragEvent) {
84
+ e.preventDefault();
85
+ if (disabled || loading) return;
86
+ isDragOver = true;
87
+ }
88
+
89
+ function handleDragLeave(e: DragEvent) {
90
+ e.preventDefault();
91
+ isDragOver = false;
92
+ }
93
+
94
+ function handleDrop(e: DragEvent) {
95
+ e.preventDefault();
96
+ isDragOver = false;
97
+ if (disabled || loading) return;
98
+ if (e.dataTransfer?.files) {
99
+ handleFiles(e.dataTransfer.files);
100
+ }
101
+ }
102
+
103
+ function handleClick() {
104
+ if (!disabled && !loading) {
105
+ fileInput?.click();
106
+ }
107
+ }
108
+
109
+ function handleKeyDown(e: KeyboardEvent) {
110
+ if ((e.key === "Enter" || e.key === " ") && !disabled && !loading) {
111
+ e.preventDefault();
112
+ fileInput?.click();
113
+ }
114
+ }
115
+ </script>
116
+
117
+ <div
118
+ role="button"
119
+ tabindex={disabled || loading ? -1 : 0}
120
+ class="plasma-dropzone {customClass}"
121
+ class:plasma-dropzone--dragover={isDragOver}
122
+ class:plasma-dropzone--disabled={disabled}
123
+ class:plasma-dropzone--loading={loading}
124
+ ondragover={handleDragOver}
125
+ ondragleave={handleDragLeave}
126
+ ondrop={handleDrop}
127
+ onclick={handleClick}
128
+ onkeydown={handleKeyDown}
129
+ {...restProps}
130
+ >
131
+ <input
132
+ bind:this={fileInput}
133
+ type="file"
134
+ {accept}
135
+ {multiple}
136
+ {disabled}
137
+ onchange={(e) => handleFiles((e.target as HTMLInputElement).files)}
138
+ class="plasma-dropzone-input"
139
+ tabindex="-1"
140
+ />
141
+
142
+ {#if loading}
143
+ <div class="plasma-dropzone-loading">
144
+ <span class="plasma-dropzone-spinner"></span>
145
+ <span class="plasma-dropzone-title">Uploading...</span>
146
+ </div>
147
+ {:else if children}
148
+ {@render children()}
149
+ {:else}
150
+ <div class="plasma-dropzone-icon">
151
+ {#if icon}
152
+ {@render icon()}
153
+ {:else}
154
+ <svg
155
+ aria-hidden="true"
156
+ viewBox="0 0 24 24"
157
+ width="40"
158
+ height="40"
159
+ fill="none"
160
+ stroke="currentColor"
161
+ stroke-width="1.8"
162
+ stroke-linecap="round"
163
+ stroke-linejoin="round"
164
+ >
165
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
166
+ <polyline points="17 8 12 3 7 8" />
167
+ <line x1="12" y1="3" x2="12" y2="15" />
168
+ </svg>
169
+ {/if}
170
+ </div>
171
+
172
+ <div class="plasma-dropzone-text">
173
+ <span class="plasma-dropzone-title">{title}</span>
174
+ {#if subtitle}
175
+ <span class="plasma-dropzone-subtitle">{subtitle}</span>
176
+ {/if}
177
+ </div>
178
+ {/if}
179
+ </div>
180
+
181
+ <style>
182
+ .plasma-dropzone {
183
+ position: relative;
184
+ display: flex;
185
+ flex-direction: column;
186
+ align-items: center;
187
+ justify-content: center;
188
+ padding: var(--plasma-space-xl) var(--plasma-space-lg);
189
+ border: 2px dashed var(--plasma-color-border);
190
+ border-radius: var(--plasma-radius-lg);
191
+ background-color: var(--plasma-color-surface-sunken);
192
+ color: var(--plasma-color-text);
193
+ font-family: var(--plasma-font-sans);
194
+ text-align: center;
195
+ cursor: pointer;
196
+ user-select: none;
197
+ box-sizing: border-box;
198
+ transition: var(--plasma-transition-fast);
199
+ width: 100%;
200
+ min-height: 160px;
201
+ gap: var(--plasma-space-sm);
202
+ }
203
+
204
+ .plasma-dropzone:hover:not(.plasma-dropzone--disabled):not(.plasma-dropzone--loading) {
205
+ border-color: var(--plasma-color-highlight);
206
+ background-color: var(--plasma-color-highlight-soft);
207
+ }
208
+
209
+ .plasma-dropzone--dragover {
210
+ border-color: var(--plasma-color-highlight) !important;
211
+ background-color: var(--plasma-color-highlight-soft) !important;
212
+ transform: scale(1.008);
213
+ }
214
+
215
+ .plasma-dropzone--disabled {
216
+ opacity: 0.5;
217
+ cursor: not-allowed;
218
+ }
219
+
220
+ .plasma-dropzone--loading {
221
+ cursor: wait;
222
+ }
223
+
224
+ .plasma-dropzone:focus-visible {
225
+ outline: 2px solid var(--plasma-color-highlight);
226
+ outline-offset: 2px;
227
+ }
228
+
229
+ .plasma-dropzone-input {
230
+ display: none;
231
+ }
232
+
233
+ .plasma-dropzone-icon {
234
+ display: inline-flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ color: var(--plasma-color-highlight);
238
+ transition: transform var(--plasma-transition-fast);
239
+ }
240
+
241
+ .plasma-dropzone:hover:not(.plasma-dropzone--disabled) .plasma-dropzone-icon {
242
+ transform: translateY(-2px);
243
+ }
244
+
245
+ .plasma-dropzone-text {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 4px;
249
+ }
250
+
251
+ .plasma-dropzone-title {
252
+ font-weight: var(--plasma-font-weight-bold);
253
+ font-size: var(--plasma-font-size-base);
254
+ color: var(--plasma-color-text);
255
+ }
256
+
257
+ .plasma-dropzone-subtitle {
258
+ font-size: var(--plasma-font-size-xs);
259
+ color: var(--plasma-color-text-muted);
260
+ }
261
+
262
+ .plasma-dropzone-loading {
263
+ display: flex;
264
+ flex-direction: column;
265
+ align-items: center;
266
+ gap: var(--plasma-space-sm);
267
+ }
268
+
269
+ .plasma-dropzone-spinner {
270
+ width: 28px;
271
+ height: 28px;
272
+ border: 3px solid var(--plasma-color-border-subtle);
273
+ border-top-color: var(--plasma-color-highlight);
274
+ border-radius: var(--plasma-radius-full);
275
+ animation: plasma-spin 0.8s linear infinite;
276
+ }
277
+
278
+ @keyframes plasma-spin {
279
+ from { transform: rotate(0deg); }
280
+ to { transform: rotate(360deg); }
281
+ }
282
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "onerror"> {
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ disabled?: boolean;
7
+ loading?: boolean;
8
+ title?: string;
9
+ subtitle?: string;
10
+ icon?: Snippet;
11
+ children?: Snippet;
12
+ onfiles?: (files: File[]) => void;
13
+ onerror?: (message: string) => void;
14
+ }
15
+ declare const DropZone: import("svelte").Component<Props, {}, "">;
16
+ type DropZone = ReturnType<typeof DropZone>;
17
+ export default DropZone;